From ebdad308630f1488c3dd11f98a5c312cf818ad54 Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 3 Mar 2026 11:45:11 +0800 Subject: [PATCH 01/62] ux: submodule diff layout Signed-off-by: leo --- src/Views/DiffView.axaml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Views/DiffView.axaml b/src/Views/DiffView.axaml index f495132a..f734a983 100644 --- a/src/Views/DiffView.axaml +++ b/src/Views/DiffView.axaml @@ -265,13 +265,14 @@ - + - - - + @@ -290,7 +291,10 @@ - + From 8db908acafbaf7095c4d11eae2efaa48a7df1e52 Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 3 Mar 2026 14:18:30 +0800 Subject: [PATCH 02/62] ux: show file path as tooltip in case of that the file's path is too long Signed-off-by: leo --- src/Views/BlameCommandPalette.axaml | 2 +- src/Views/FileHistoryCommandPalette.axaml | 2 +- src/Views/OpenFileCommandPalette.axaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Views/BlameCommandPalette.axaml b/src/Views/BlameCommandPalette.axaml index b20ffa51..7d65605e 100644 --- a/src/Views/BlameCommandPalette.axaml +++ b/src/Views/BlameCommandPalette.axaml @@ -88,7 +88,7 @@ - + - + - + Date: Tue, 3 Mar 2026 14:39:12 +0800 Subject: [PATCH 03/62] ux: repository toolbar buttons layout Signed-off-by: leo --- src/Views/RepositoryToolbar.axaml | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/Views/RepositoryToolbar.axaml b/src/Views/RepositoryToolbar.axaml index be86aa0c..f857ffee 100644 --- a/src/Views/RepositoryToolbar.axaml +++ b/src/Views/RepositoryToolbar.axaml @@ -7,7 +7,7 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="SourceGit.Views.RepositoryToolbar" x:DataType="vm:Repository"> - + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Views/ExecuteCustomActionCommandPalette.axaml.cs b/src/Views/ExecuteCustomActionCommandPalette.axaml.cs new file mode 100644 index 00000000..8986b3b7 --- /dev/null +++ b/src/Views/ExecuteCustomActionCommandPalette.axaml.cs @@ -0,0 +1,63 @@ +using Avalonia.Controls; +using Avalonia.Input; + +namespace SourceGit.Views +{ + public partial class ExecuteCustomActionCommandPalette : UserControl + { + public ExecuteCustomActionCommandPalette() + { + InitializeComponent(); + } + + protected override async void OnKeyDown(KeyEventArgs e) + { + base.OnKeyDown(e); + + if (DataContext is not ViewModels.ExecuteCustomActionCommandPalette vm) + return; + + if (e.Key == Key.Enter) + { + await vm.ExecAsync(); + e.Handled = true; + } + else if (e.Key == Key.Up) + { + if (ActionListBox.IsKeyboardFocusWithin) + { + FilterTextBox.Focus(NavigationMethod.Directional); + e.Handled = true; + return; + } + } + else if (e.Key == Key.Down || e.Key == Key.Tab) + { + if (FilterTextBox.IsKeyboardFocusWithin) + { + if (vm.VisibleActions.Count > 0) + ActionListBox.Focus(NavigationMethod.Directional); + + e.Handled = true; + return; + } + + if (ActionListBox.IsKeyboardFocusWithin && e.Key == Key.Tab) + { + FilterTextBox.Focus(NavigationMethod.Directional); + e.Handled = true; + return; + } + } + } + + private async void OnItemTapped(object sender, TappedEventArgs e) + { + if (DataContext is ViewModels.ExecuteCustomActionCommandPalette vm) + { + await vm.ExecAsync(); + e.Handled = true; + } + } + } +} diff --git a/src/Views/RepositoryCommandPalette.axaml b/src/Views/RepositoryCommandPalette.axaml index f3975c6d..adf59e46 100644 --- a/src/Views/RepositoryCommandPalette.axaml +++ b/src/Views/RepositoryCommandPalette.axaml @@ -50,7 +50,7 @@ Date: Fri, 6 Mar 2026 15:54:14 +0800 Subject: [PATCH 23/62] feature: add a new commit message trailer - `Milestone:` Signed-off-by: leo --- src/Views/CommitMessageToolBox.axaml.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Views/CommitMessageToolBox.axaml.cs b/src/Views/CommitMessageToolBox.axaml.cs index 831b50dc..52579583 100644 --- a/src/Views/CommitMessageToolBox.axaml.cs +++ b/src/Views/CommitMessageToolBox.axaml.cs @@ -385,6 +385,7 @@ namespace SourceGit.Views "Fixes: ", "Helped-by: ", "Issue: ", + "Milestone: ", "on-behalf-of: @", "Reference-to: ", "Refs: ", From f356a7bc6349598003c2a62f8967019c95ed3a11 Mon Sep 17 00:00:00 2001 From: leo Date: Fri, 6 Mar 2026 17:58:26 +0800 Subject: [PATCH 24/62] refactor: re-design command palette API Signed-off-by: leo --- src/ViewModels/BlameCommandPalette.cs | 20 +-- src/ViewModels/CheckoutCommandPalette.cs | 19 +-- src/ViewModels/CompareCommandPalette.cs | 18 +- .../ExecuteCustomActionCommandPalette.cs | 21 +-- src/ViewModels/FileHistoryCommandPalette.cs | 19 +-- src/ViewModels/ICommandPalette.cs | 16 +- src/ViewModels/Launcher.cs | 19 +-- src/ViewModels/LauncherPagesCommandPalette.cs | 18 +- src/ViewModels/MergeCommandPalette.cs | 18 +- src/ViewModels/OpenFileCommandPalette.cs | 20 +-- src/ViewModels/RepositoryCommandPalette.cs | 155 +++++------------- src/Views/BranchTree.axaml.cs | 12 +- src/Views/Launcher.axaml.cs | 12 +- src/Views/RepositoryCommandPaletteTextBox.cs | 2 +- src/Views/TagsView.axaml.cs | 4 +- 15 files changed, 96 insertions(+), 277 deletions(-) diff --git a/src/ViewModels/BlameCommandPalette.cs b/src/ViewModels/BlameCommandPalette.cs index fa24faf6..c277a47c 100644 --- a/src/ViewModels/BlameCommandPalette.cs +++ b/src/ViewModels/BlameCommandPalette.cs @@ -35,9 +35,8 @@ namespace SourceGit.ViewModels set => SetProperty(ref _selectedFile, value); } - public BlameCommandPalette(Launcher launcher, string repo) + public BlameCommandPalette(string repo) { - _launcher = launcher; _repo = repo; _isLoading = true; @@ -61,17 +60,6 @@ namespace SourceGit.ViewModels }); } - public override void Cleanup() - { - _launcher = null; - _repo = null; - _head = null; - _repoFiles.Clear(); - _filter = null; - _visibleFiles.Clear(); - _selectedFile = null; - } - public void ClearFilter() { Filter = string.Empty; @@ -79,9 +67,12 @@ namespace SourceGit.ViewModels public void Launch() { + _repoFiles.Clear(); + _visibleFiles.Clear(); + Close(); + if (!string.IsNullOrEmpty(_selectedFile)) App.ShowWindow(new Blame(_repo, _selectedFile, _head)); - _launcher.CancelCommandPalette(); } private void UpdateVisible() @@ -117,7 +108,6 @@ namespace SourceGit.ViewModels } } - private Launcher _launcher = null; private string _repo = null; private bool _isLoading = false; private Models.Commit _head = null; diff --git a/src/ViewModels/CheckoutCommandPalette.cs b/src/ViewModels/CheckoutCommandPalette.cs index 2be5ddb0..480556bc 100644 --- a/src/ViewModels/CheckoutCommandPalette.cs +++ b/src/ViewModels/CheckoutCommandPalette.cs @@ -28,22 +28,12 @@ namespace SourceGit.ViewModels } } - public CheckoutCommandPalette(Launcher launcher, Repository repo) + public CheckoutCommandPalette(Repository repo) { - _launcher = launcher; _repo = repo; UpdateBranches(); } - public override void Cleanup() - { - _launcher = null; - _repo = null; - _branches.Clear(); - _selectedBranch = null; - _filter = null; - } - public void ClearFilter() { Filter = string.Empty; @@ -51,13 +41,11 @@ namespace SourceGit.ViewModels public async Task ExecAsync() { - _launcher.CommandPalette = null; + _branches.Clear(); + Close(); if (_selectedBranch != null) await _repo.CheckoutBranchAsync(_selectedBranch); - - Dispose(); - GC.Collect(); } private void UpdateBranches() @@ -94,7 +82,6 @@ namespace SourceGit.ViewModels SelectedBranch = autoSelected; } - private Launcher _launcher; private Repository _repo; private List _branches = []; private Models.Branch _selectedBranch = null; diff --git a/src/ViewModels/CompareCommandPalette.cs b/src/ViewModels/CompareCommandPalette.cs index 6fe1a43d..bebffe37 100644 --- a/src/ViewModels/CompareCommandPalette.cs +++ b/src/ViewModels/CompareCommandPalette.cs @@ -32,24 +32,13 @@ namespace SourceGit.ViewModels } } - public CompareCommandPalette(Launcher launcher, Repository repo, object basedOn) + public CompareCommandPalette(Repository repo, object basedOn) { - _launcher = launcher; _repo = repo; _basedOn = basedOn ?? repo.CurrentBranch; UpdateRefs(); } - public override void Cleanup() - { - _launcher = null; - _repo = null; - _basedOn = null; - _compareTo = null; - _refs.Clear(); - _filter = null; - } - public void ClearFilter() { Filter = string.Empty; @@ -57,9 +46,11 @@ namespace SourceGit.ViewModels public void Launch() { + _refs.Clear(); + Close(); + if (_compareTo != null) App.ShowWindow(new Compare(_repo, _basedOn, _compareTo)); - _launcher?.CancelCommandPalette(); } private void UpdateRefs() @@ -114,7 +105,6 @@ namespace SourceGit.ViewModels CompareTo = autoSelected; } - private Launcher _launcher; private Repository _repo; private object _basedOn = null; private object _compareTo = null; diff --git a/src/ViewModels/ExecuteCustomActionCommandPalette.cs b/src/ViewModels/ExecuteCustomActionCommandPalette.cs index 8c50b044..78290086 100644 --- a/src/ViewModels/ExecuteCustomActionCommandPalette.cs +++ b/src/ViewModels/ExecuteCustomActionCommandPalette.cs @@ -41,9 +41,8 @@ namespace SourceGit.ViewModels } } - public ExecuteCustomActionCommandPalette(Launcher launcher, Repository repo) + public ExecuteCustomActionCommandPalette(Repository repo) { - _launcher = launcher; _repo = repo; var actions = repo.GetCustomActions(Models.CustomActionScope.Repository); @@ -65,16 +64,6 @@ namespace SourceGit.ViewModels } } - public override void Cleanup() - { - _launcher = null; - _repo = null; - _actions.Clear(); - _visibleActions.Clear(); - _selected = null; - _filter = null; - } - public void ClearFilter() { Filter = string.Empty; @@ -82,13 +71,12 @@ namespace SourceGit.ViewModels public async Task ExecAsync() { - _launcher.CommandPalette = null; + _actions.Clear(); + _visibleActions.Clear(); + Close(); if (_selected != null) await _repo.ExecCustomActionAsync(_selected.Action, null); - - Dispose(); - GC.Collect(); } private void UpdateVisibleActions() @@ -117,7 +105,6 @@ namespace SourceGit.ViewModels Selected = autoSelected; } - private Launcher _launcher; private Repository _repo; private List _actions = []; private List _visibleActions = []; diff --git a/src/ViewModels/FileHistoryCommandPalette.cs b/src/ViewModels/FileHistoryCommandPalette.cs index fa243816..8a371feb 100644 --- a/src/ViewModels/FileHistoryCommandPalette.cs +++ b/src/ViewModels/FileHistoryCommandPalette.cs @@ -35,9 +35,8 @@ namespace SourceGit.ViewModels set => SetProperty(ref _selectedFile, value); } - public FileHistoryCommandPalette(Launcher launcher, string repo) + public FileHistoryCommandPalette(string repo) { - _launcher = launcher; _repo = repo; _isLoading = true; @@ -56,16 +55,6 @@ namespace SourceGit.ViewModels }); } - public override void Cleanup() - { - _launcher = null; - _repo = null; - _repoFiles.Clear(); - _filter = null; - _visibleFiles.Clear(); - _selectedFile = null; - } - public void ClearFilter() { Filter = string.Empty; @@ -73,9 +62,12 @@ namespace SourceGit.ViewModels public void Launch() { + _repoFiles.Clear(); + _visibleFiles.Clear(); + Close(); + if (!string.IsNullOrEmpty(_selectedFile)) App.ShowWindow(new FileHistories(_repo, _selectedFile)); - _launcher.CancelCommandPalette(); } private void UpdateVisible() @@ -111,7 +103,6 @@ namespace SourceGit.ViewModels } } - private Launcher _launcher = null; private string _repo = null; private bool _isLoading = false; private List _repoFiles = null; diff --git a/src/ViewModels/ICommandPalette.cs b/src/ViewModels/ICommandPalette.cs index 232a8558..e4682912 100644 --- a/src/ViewModels/ICommandPalette.cs +++ b/src/ViewModels/ICommandPalette.cs @@ -1,17 +1,21 @@ -using System; -using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.ComponentModel; namespace SourceGit.ViewModels { - public class ICommandPalette : ObservableObject, IDisposable + public class ICommandPalette : ObservableObject { - public void Dispose() + public void Open() { - Cleanup(); + var host = App.GetLauncher(); + if (host != null) + host.CommandPalette = this; } - public virtual void Cleanup() + public void Close() { + var host = App.GetLauncher(); + if (host != null) + host.CommandPalette = null; } } } diff --git a/src/ViewModels/Launcher.cs b/src/ViewModels/Launcher.cs index b2c072ad..ef34d4df 100644 --- a/src/ViewModels/Launcher.cs +++ b/src/ViewModels/Launcher.cs @@ -347,23 +347,6 @@ namespace SourceGit.ViewModels ActivePage = page; } - public void OpenCommandPalette(ICommandPalette commandPalette) - { - var old = _commandPalette; - CommandPalette = commandPalette; - old?.Dispose(); - } - - public void CancelCommandPalette() - { - if (_commandPalette != null) - { - _commandPalette?.Dispose(); - CommandPalette = null; - GC.Collect(); - } - } - public void DispatchNotification(string pageId, string message, bool isError) { if (!Dispatcher.UIThread.CheckAccess()) @@ -453,7 +436,7 @@ namespace SourceGit.ViewModels builder.Append(" - ").Append(_activeWorkspace.Name); Title = builder.ToString(); - CancelCommandPalette(); + CommandPalette = null; } private Workspace _activeWorkspace; diff --git a/src/ViewModels/LauncherPagesCommandPalette.cs b/src/ViewModels/LauncherPagesCommandPalette.cs index 45a065da..265aa070 100644 --- a/src/ViewModels/LauncherPagesCommandPalette.cs +++ b/src/ViewModels/LauncherPagesCommandPalette.cs @@ -60,17 +60,6 @@ namespace SourceGit.ViewModels UpdateVisible(); } - public override void Cleanup() - { - _launcher = null; - _opened.Clear(); - _visiblePages.Clear(); - _visibleRepos.Clear(); - _searchFilter = null; - _selectedPage = null; - _selectedRepo = null; - } - public void ClearFilter() { SearchFilter = string.Empty; @@ -78,12 +67,15 @@ namespace SourceGit.ViewModels public void OpenOrSwitchTo() { + _opened.Clear(); + _visiblePages.Clear(); + _visibleRepos.Clear(); + Close(); + if (_selectedPage != null) _launcher.ActivePage = _selectedPage; else if (_selectedRepo != null) _launcher.OpenRepositoryInTab(_selectedRepo, null); - - _launcher?.CancelCommandPalette(); } private void UpdateVisible() diff --git a/src/ViewModels/MergeCommandPalette.cs b/src/ViewModels/MergeCommandPalette.cs index 119c242b..745bcbc0 100644 --- a/src/ViewModels/MergeCommandPalette.cs +++ b/src/ViewModels/MergeCommandPalette.cs @@ -27,22 +27,12 @@ namespace SourceGit.ViewModels set => SetProperty(ref _selectedBranch, value); } - public MergeCommandPalette(Launcher launcher, Repository repo) + public MergeCommandPalette(Repository repo) { - _launcher = launcher; _repo = repo; UpdateBranches(); } - public override void Cleanup() - { - _launcher = null; - _repo = null; - _branches.Clear(); - _filter = null; - _selectedBranch = null; - } - public void ClearFilter() { Filter = string.Empty; @@ -50,10 +40,11 @@ namespace SourceGit.ViewModels public void Launch() { + _branches.Clear(); + Close(); + if (_repo.CanCreatePopup() && _selectedBranch != null) _repo.ShowPopup(new Merge(_repo, _selectedBranch, _repo.CurrentBranch.Name, false)); - - _launcher?.CancelCommandPalette(); } private void UpdateBranches() @@ -90,7 +81,6 @@ namespace SourceGit.ViewModels SelectedBranch = autoSelected; } - private Launcher _launcher = null; private Repository _repo = null; private List _branches = new List(); private string _filter = string.Empty; diff --git a/src/ViewModels/OpenFileCommandPalette.cs b/src/ViewModels/OpenFileCommandPalette.cs index 2ca5b706..78a702e0 100644 --- a/src/ViewModels/OpenFileCommandPalette.cs +++ b/src/ViewModels/OpenFileCommandPalette.cs @@ -35,9 +35,8 @@ namespace SourceGit.ViewModels set => SetProperty(ref _selectedFile, value); } - public OpenFileCommandPalette(Launcher launcher, string repo) + public OpenFileCommandPalette(string repo) { - _launcher = launcher; _repo = repo; _isLoading = true; @@ -56,16 +55,6 @@ namespace SourceGit.ViewModels }); } - public override void Cleanup() - { - _launcher = null; - _repo = null; - _repoFiles.Clear(); - _filter = null; - _visibleFiles.Clear(); - _selectedFile = null; - } - public void ClearFilter() { Filter = string.Empty; @@ -73,10 +62,12 @@ namespace SourceGit.ViewModels public void Launch() { + _repoFiles.Clear(); + _visibleFiles.Clear(); + Close(); + if (!string.IsNullOrEmpty(_selectedFile)) Native.OS.OpenWithDefaultEditor(Native.OS.GetAbsPath(_repo, _selectedFile)); - - _launcher.CancelCommandPalette(); } private void UpdateVisible() @@ -112,7 +103,6 @@ namespace SourceGit.ViewModels } } - private Launcher _launcher = null; private string _repo = null; private bool _isLoading = false; private List _repoFiles = null; diff --git a/src/ViewModels/RepositoryCommandPalette.cs b/src/ViewModels/RepositoryCommandPalette.cs index cc222443..6d6c77c8 100644 --- a/src/ViewModels/RepositoryCommandPalette.cs +++ b/src/ViewModels/RepositoryCommandPalette.cs @@ -8,15 +8,26 @@ namespace SourceGit.ViewModels public string Label { get; set; } public string Keyword { get; set; } public string Icon { get; set; } + public bool CloseBeforeExec { get; set; } public Action Action { get; set; } - public RepositoryCommandPaletteCmd(string label, string keyword, string icon, Action action) + public RepositoryCommandPaletteCmd(string labelKey, string keyword, string icon, Action action) { - Label = label; + Label = $"{App.Text(labelKey)}..."; Keyword = keyword; Icon = icon; + CloseBeforeExec = true; Action = action; } + + public RepositoryCommandPaletteCmd(string labelKey, string keyword, string icon, ICommandPalette child) + { + Label = $"{App.Text(labelKey)}..."; + Keyword = keyword; + Icon = icon; + CloseBeforeExec = false; + Action = () => child.Open(); + } } public class RepositoryCommandPalette : ICommandPalette @@ -43,124 +54,32 @@ namespace SourceGit.ViewModels } } - public RepositoryCommandPalette(Launcher launcher, Repository repo) + public RepositoryCommandPalette(Repository repo) { - _launcher = launcher; - _repo = repo; + // Sub-CommandPalettes + _cmds.Add(new("Blame", "blame", "Blame", new BlameCommandPalette(repo.FullPath))); + _cmds.Add(new("Checkout", "checkout", "Check", new CheckoutCommandPalette(repo))); + _cmds.Add(new("Compare.WithHead", "compare", "Compare", new CompareCommandPalette(repo, null))); + _cmds.Add(new("FileHistory", "history", "Histories", new FileHistoryCommandPalette(repo.FullPath))); + _cmds.Add(new("Merge", "merge", "Merge", new MergeCommandPalette(repo))); + _cmds.Add(new("OpenFile", "open", "OpenWith", new OpenFileCommandPalette(repo.FullPath))); + _cmds.Add(new("Repository.CustomActions", "custom actions", "Action", new ExecuteCustomActionCommandPalette(repo))); - _cmds.Add(new($"{App.Text("Blame")}...", "blame", "Blame", () => - { - var sub = new BlameCommandPalette(_launcher, _repo.FullPath); - _launcher.OpenCommandPalette(sub); - })); - - _cmds.Add(new($"{App.Text("Checkout")}...", "checkout", "Check", () => - { - var sub = new CheckoutCommandPalette(_launcher, _repo); - _launcher.OpenCommandPalette(sub); - })); - - _cmds.Add(new($"{App.Text("Compare.WithHead")}...", "compare", "Compare", () => - { - var sub = new CompareCommandPalette(_launcher, _repo, _repo.CurrentBranch); - _launcher.OpenCommandPalette(sub); - })); - - _cmds.Add(new($"{App.Text("FileHistory")}...", "history", "Histories", () => - { - var sub = new FileHistoryCommandPalette(_launcher, _repo.FullPath); - _launcher.OpenCommandPalette(sub); - })); - - _cmds.Add(new($"{App.Text("Merge")}...", "merge", "Merge", () => - { - var sub = new MergeCommandPalette(_launcher, _repo); - _launcher.OpenCommandPalette(sub); - })); - - _cmds.Add(new($"{App.Text("OpenFile")}...", "open", "OpenWith", () => - { - var sub = new OpenFileCommandPalette(_launcher, _repo.FullPath); - _launcher.OpenCommandPalette(sub); - })); - - _cmds.Add(new($"{App.Text("Repository.NewBranch")}...", "create branch", "Branch.Add", () => - { - var repo = _repo; - _launcher.CancelCommandPalette(); - repo.CreateNewBranch(); - })); - - _cmds.Add(new($"{App.Text("CreateTag.Title")}...", "create tag", "Tag.Add", () => - { - var repo = _repo; - _launcher.CancelCommandPalette(); - repo.CreateNewTag(); - })); - - _cmds.Add(new($"{App.Text("Fetch")}...", "fetch", "Fetch", async () => - { - var repo = _repo; - _launcher.CancelCommandPalette(); - await repo.FetchAsync(false); - })); - - _cmds.Add(new($"{App.Text("Pull.Title")}...", "pull", "Pull", async () => - { - var repo = _repo; - _launcher.CancelCommandPalette(); - await repo.PullAsync(false); - })); - - _cmds.Add(new($"{App.Text("Push")}...", "push", "Push", async () => - { - var repo = _repo; - _launcher.CancelCommandPalette(); - await repo.PushAsync(false); - })); - - _cmds.Add(new($"{App.Text("Stash.Title")}...", "stash", "Stashes.Add", async () => - { - var repo = _repo; - _launcher.CancelCommandPalette(); - await repo.StashAllAsync(false); - })); - - _cmds.Add(new($"{App.Text("Apply.Title")}...", "apply", "Diff", () => - { - var repo = _repo; - _launcher.CancelCommandPalette(); - repo.ApplyPatch(); - })); - - _cmds.Add(new($"{App.Text("Configure")}...", "configure", "Settings", async () => - { - var repo = _repo; - _launcher.CancelCommandPalette(); - await App.ShowDialog(new RepositoryConfigure(repo)); - })); - - _cmds.Add(new($"{App.Text("Repository.CustomActions")}...", "custom actions", "Action", async () => - { - var sub = new ExecuteCustomActionCommandPalette(_launcher, _repo); - _launcher.OpenCommandPalette(sub); - })); + // Raw-Actions + _cmds.Add(new("Repository.NewBranch", "create branch", "Branch.Add", () => repo.CreateNewBranch())); + _cmds.Add(new("CreateTag.Title", "create tag", "Tag.Add", () => repo.CreateNewTag())); + _cmds.Add(new("Fetch", "fetch", "Fetch", async () => await repo.FetchAsync(false))); + _cmds.Add(new("Pull.Title", "pull", "Pull", async () => await repo.PullAsync(false))); + _cmds.Add(new("Push", "push", "Push", async () => await repo.PushAsync(false))); + _cmds.Add(new("Stash.Title", "stash", "Stashes.Add", async () => await repo.StashAllAsync(false))); + _cmds.Add(new("Apply.Title", "apply", "Diff", () => repo.ApplyPatch())); + _cmds.Add(new("Configure", "configure", "Settings", async () => await App.ShowDialog(new RepositoryConfigure(repo)))); _cmds.Sort((l, r) => l.Label.CompareTo(r.Label)); _visibleCmds = _cmds; _selectedCmd = _cmds[0]; } - public override void Cleanup() - { - _launcher = null; - _repo = null; - _cmds.Clear(); - _visibleCmds.Clear(); - _selectedCmd = null; - _filter = null; - } - public void ClearFilter() { Filter = string.Empty; @@ -168,10 +87,16 @@ namespace SourceGit.ViewModels public void Exec() { + _cmds.Clear(); + _visibleCmds.Clear(); + if (_selectedCmd != null) + { + if (_selectedCmd.CloseBeforeExec) + Close(); + _selectedCmd.Action?.Invoke(); - else - _launcher?.CancelCommandPalette(); + } } private void UpdateVisible() @@ -200,8 +125,6 @@ namespace SourceGit.ViewModels } } - private Launcher _launcher = null; - private Repository _repo = null; private List _cmds = []; private List _visibleCmds = []; private RepositoryCommandPaletteCmd _selectedCmd = null; diff --git a/src/Views/BranchTree.axaml.cs b/src/Views/BranchTree.axaml.cs index dd1f4a0e..65113340 100644 --- a/src/Views/BranchTree.axaml.cs +++ b/src/Views/BranchTree.axaml.cs @@ -738,9 +738,7 @@ namespace SourceGit.Views compareWith.Icon = App.CreateMenuIcon("Icons.Compare"); compareWith.Click += (_, _) => { - var launcher = App.GetLauncher(); - if (launcher != null) - launcher.OpenCommandPalette(new ViewModels.CompareCommandPalette(launcher, repo, branch)); + new ViewModels.CompareCommandPalette(repo, branch).Open(); }; menu.Items.Add(new MenuItem() { Header = "-" }); menu.Items.Add(compareWith); @@ -863,9 +861,7 @@ namespace SourceGit.Views compareWith.Icon = App.CreateMenuIcon("Icons.Compare"); compareWith.Click += (_, _) => { - var launcher = App.GetLauncher(); - if (launcher != null) - launcher.OpenCommandPalette(new ViewModels.CompareCommandPalette(launcher, repo, branch)); + new ViewModels.CompareCommandPalette(repo, branch).Open(); }; menu.Items.Add(new MenuItem() { Header = "-" }); menu.Items.Add(compareWithCurrent); @@ -1171,9 +1167,7 @@ namespace SourceGit.Views compareWith.Icon = App.CreateMenuIcon("Icons.Compare"); compareWith.Click += (_, _) => { - var launcher = App.GetLauncher(); - if (launcher != null) - launcher.OpenCommandPalette(new ViewModels.CompareCommandPalette(launcher, repo, branch)); + new ViewModels.CompareCommandPalette(repo, branch).Open(); }; menu.Items.Add(pull); diff --git a/src/Views/Launcher.axaml.cs b/src/Views/Launcher.axaml.cs index 541b3452..744f5352 100644 --- a/src/Views/Launcher.axaml.cs +++ b/src/Views/Launcher.axaml.cs @@ -244,7 +244,7 @@ namespace SourceGit.Views e.Handled = true; return; case Key.P when e.KeyModifiers.HasFlag(KeyModifiers.Shift): - vm.OpenCommandPalette(new ViewModels.RepositoryCommandPalette(vm, repo)); + vm.CommandPalette = new ViewModels.RepositoryCommandPalette(repo); e.Handled = true; return; } @@ -266,7 +266,7 @@ namespace SourceGit.Views else if (e.Key == Key.Escape) { if (vm.CommandPalette != null) - vm.CancelCommandPalette(); + vm.CommandPalette = null; else vm.ActivePage.CancelPopup(); @@ -372,15 +372,15 @@ namespace SourceGit.Views private void OnOpenPagesCommandPalette(object sender, RoutedEventArgs e) { - if (DataContext is ViewModels.Launcher launcher) - launcher.OpenCommandPalette(new ViewModels.LauncherPagesCommandPalette(launcher)); + if (DataContext is ViewModels.Launcher vm) + vm.CommandPalette = new ViewModels.LauncherPagesCommandPalette(vm); e.Handled = true; } private void OnCloseCommandPalette(object sender, PointerPressedEventArgs e) { - if (e.Source == sender && DataContext is ViewModels.Launcher launcher) - launcher.CancelCommandPalette(); + if (e.Source == sender && DataContext is ViewModels.Launcher vm) + vm.CommandPalette = null; e.Handled = true; } diff --git a/src/Views/RepositoryCommandPaletteTextBox.cs b/src/Views/RepositoryCommandPaletteTextBox.cs index 9e0b7e3c..cc44c077 100644 --- a/src/Views/RepositoryCommandPaletteTextBox.cs +++ b/src/Views/RepositoryCommandPaletteTextBox.cs @@ -18,7 +18,7 @@ namespace SourceGit.Views if (launcherView is { DataContext: ViewModels.Launcher launcher } && launcher.ActivePage is { Data: ViewModels.Repository repo }) { - launcher.OpenCommandPalette(new ViewModels.RepositoryCommandPalette(launcher, repo)); + launcher.CommandPalette = new ViewModels.RepositoryCommandPalette(repo); e.Handled = true; return; } diff --git a/src/Views/TagsView.axaml.cs b/src/Views/TagsView.axaml.cs index f33012cd..d27ff473 100644 --- a/src/Views/TagsView.axaml.cs +++ b/src/Views/TagsView.axaml.cs @@ -279,9 +279,7 @@ namespace SourceGit.Views compareWith.Icon = App.CreateMenuIcon("Icons.Compare"); compareWith.Click += (_, _) => { - var launcher = App.GetLauncher(); - if (launcher != null) - launcher.OpenCommandPalette(new ViewModels.CompareCommandPalette(launcher, repo, tag)); + new ViewModels.CompareCommandPalette(repo, tag).Open(); }; var archive = new MenuItem(); From 2f06b9eb7d31d10bbc5ef3b3c767aa8f82b45a4b Mon Sep 17 00:00:00 2001 From: leo Date: Fri, 6 Mar 2026 18:11:46 +0800 Subject: [PATCH 25/62] enhance: use `App.GetLauncher()` instead of `FindAncestorOfType` Signed-off-by: leo --- src/Views/RepositoryCommandPaletteTextBox.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Views/RepositoryCommandPaletteTextBox.cs b/src/Views/RepositoryCommandPaletteTextBox.cs index cc44c077..ad33fe2d 100644 --- a/src/Views/RepositoryCommandPaletteTextBox.cs +++ b/src/Views/RepositoryCommandPaletteTextBox.cs @@ -1,8 +1,8 @@ using System; + using Avalonia; using Avalonia.Controls; using Avalonia.Input; -using Avalonia.VisualTree; namespace SourceGit.Views { @@ -14,9 +14,8 @@ namespace SourceGit.Views { if (e.Key == Key.Back && string.IsNullOrEmpty(Text)) { - var launcherView = this.FindAncestorOfType(false); - if (launcherView is { DataContext: ViewModels.Launcher launcher } && - launcher.ActivePage is { Data: ViewModels.Repository repo }) + var launcher = App.GetLauncher(); + if (launcher is { ActivePage: { Data: ViewModels.Repository repo } }) { launcher.CommandPalette = new ViewModels.RepositoryCommandPalette(repo); e.Handled = true; From e78815efb101fe4ddfcf9dc57af9cb6491cb1592 Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 9 Mar 2026 13:01:08 +0800 Subject: [PATCH 26/62] =?UTF-8?q?enhance:=20block=20other=20hotkeys=20when?= =?UTF-8?q?=20there=20is=20a=20command=20palette=20(#2173=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: leo --- src/Views/Launcher.axaml | 4 ++- src/Views/Launcher.axaml.cs | 38 ++++++++++++++++++++++++---- src/Views/LauncherTabBar.axaml | 2 +- src/Views/RepositoryToolbar.axaml.cs | 9 +++++++ src/Views/WorkingCopy.axaml.cs | 9 +++++++ 5 files changed, 55 insertions(+), 7 deletions(-) diff --git a/src/Views/Launcher.axaml b/src/Views/Launcher.axaml index 303388cd..b1d3dfae 100644 --- a/src/Views/Launcher.axaml +++ b/src/Views/Launcher.axaml @@ -127,7 +127,9 @@ + PointerPressed="OnCloseCommandPalette" + KeyDown="OnCommandPaletteKeyDown" + KeyUp="OnCommandPaletteKeyUp"> diff --git a/src/Views/Launcher.axaml.cs b/src/Views/Launcher.axaml.cs index 744f5352..3bcb2db5 100644 --- a/src/Views/Launcher.axaml.cs +++ b/src/Views/Launcher.axaml.cs @@ -131,6 +131,11 @@ namespace SourceGit.Views ViewModels.Preferences.Instance.Layout.LauncherWindowState = state; } + else if (change.Property == IsActiveProperty) + { + if (!IsActive && DataContext is ViewModels.Launcher { CommandPalette: { } } vm) + vm.CommandPalette = null; + } } protected override void OnSizeChanged(SizeChangedEventArgs e) @@ -203,6 +208,13 @@ namespace SourceGit.Views return; } + if (e.Key == Key.T) + { + vm.AddNewTab(); + e.Handled = true; + return; + } + if ((OperatingSystem.IsMacOS() && e.KeyModifiers.HasFlag(KeyModifiers.Alt) && e.Key == Key.Right) || (!OperatingSystem.IsMacOS() && !e.KeyModifiers.HasFlag(KeyModifiers.Shift) && e.Key == Key.Tab)) { @@ -265,11 +277,7 @@ namespace SourceGit.Views } else if (e.Key == Key.Escape) { - if (vm.CommandPalette != null) - vm.CommandPalette = null; - else - vm.ActivePage.CancelPopup(); - + vm.ActivePage.CancelPopup(); e.Handled = true; return; } @@ -317,6 +325,9 @@ namespace SourceGit.Views { if (sender is Button btn && DataContext is ViewModels.Launcher launcher) { + if (launcher.CommandPalette != null) + launcher.CommandPalette = null; + var pref = ViewModels.Preferences.Instance; var menu = new ContextMenu(); menu.Placement = PlacementMode.BottomEdgeAlignedLeft; @@ -384,6 +395,23 @@ namespace SourceGit.Views e.Handled = true; } + private void OnCommandPaletteKeyDown(object sender, KeyEventArgs e) + { + if (DataContext is ViewModels.Launcher { CommandPalette: { } } vm) + { + if (e.Key == Key.Escape) + vm.CommandPalette = null; + + e.Route = RoutingStrategies.Direct; + e.Handled = true; + } + } + + private void OnCommandPaletteKeyUp(object sender, KeyEventArgs e) + { + e.Handled = true; + } + private WindowState _lastWindowState = WindowState.Normal; } } diff --git a/src/Views/LauncherTabBar.axaml b/src/Views/LauncherTabBar.axaml index cde6d226..f80c1795 100644 --- a/src/Views/LauncherTabBar.axaml +++ b/src/Views/LauncherTabBar.axaml @@ -153,7 +153,7 @@ -