From 52b51ffb160fb44531ac4f049daa01ae993efacf Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 13 Apr 2026 11:20:42 +0800 Subject: [PATCH 01/23] enhance: AI-based commit message generator (#2255) - Make `AI Assistant` window resizable - Make `AI Assistant` window non-modal dialog - Clean up code Signed-off-by: leo --- src/Views/AIAssistant.axaml | 9 ++++----- src/Views/CommitMessageToolBox.axaml.cs | 19 +++++++++++++++---- src/Views/WorkingCopy.axaml.cs | 20 ++++++++++++++++---- 3 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/Views/AIAssistant.axaml b/src/Views/AIAssistant.axaml index d71114394..b4c496eac 100644 --- a/src/Views/AIAssistant.axaml +++ b/src/Views/AIAssistant.axaml @@ -4,16 +4,16 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:v="using:SourceGit.Views" xmlns:vm="using:SourceGit.ViewModels" - mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="120" + mc:Ignorable="d" d:DesignWidth="520" d:DesignHeight="400" x:Class="SourceGit.Views.AIAssistant" x:DataType="vm:AIAssistant" x:Name="ThisControl" Icon="/App.ico" Title="{DynamicResource Text.AIAssistant}" - Width="520" SizeToContent="Height" - CanResize="False" + Width="520" Height="400" + CanResize="True" WindowStartupLocation="CenterOwner"> - + + item.Click += (_, ev) => { - await App.ShowDialog(new ViewModels.AIAssistant(repo, dup, vm.Staged)); + DoOpenAIAssistant(repo, dup, vm.Staged); ev.Handled = true; }; @@ -637,5 +637,16 @@ private void OnOpenConventionalCommitHelper(object _, RoutedEventArgs e) e.Handled = true; } + + private void DoOpenAIAssistant(ViewModels.Repository repo, AI.Service service, List changes) + { + var owner = TopLevel.GetTopLevel(this) as Window; + if (owner == null) + return; + + var assistant = new ViewModels.AIAssistant(repo, service, changes); + var view = new AIAssistant() { DataContext = assistant }; + view.Show(owner); + } } } diff --git a/src/Views/WorkingCopy.axaml.cs b/src/Views/WorkingCopy.axaml.cs index febc0853d..ea7a215d2 100644 --- a/src/Views/WorkingCopy.axaml.cs +++ b/src/Views/WorkingCopy.axaml.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using Avalonia.Controls; @@ -927,9 +928,9 @@ public ContextMenu CreateContextMenuForStagedChanges(ViewModels.WorkingCopy vm, if (services.Count == 1) { - ai.Click += async (_, e) => + ai.Click += (_, e) => { - await App.ShowDialog(new ViewModels.AIAssistant(repo, services[0], selectedStaged)); + DoOpenAIAssistant(repo, services[0], selectedStaged); e.Handled = true; }; } @@ -941,9 +942,9 @@ public ContextMenu CreateContextMenuForStagedChanges(ViewModels.WorkingCopy vm, var item = new MenuItem(); item.Header = service.Name; - item.Click += async (_, e) => + item.Click += (_, e) => { - await App.ShowDialog(new ViewModels.AIAssistant(repo, dup, selectedStaged)); + DoOpenAIAssistant(repo, dup, selectedStaged); e.Handled = true; }; @@ -1369,5 +1370,16 @@ private void TryToAddCustomActionsToContextMenu(ViewModels.Repository repo, Cont menu.Items.Add(custom); menu.Items.Add(new MenuItem() { Header = "-" }); } + + private void DoOpenAIAssistant(ViewModels.Repository repo, AI.Service serivce, List changes) + { + var owner = TopLevel.GetTopLevel(this) as Window; + if (owner == null) + return; + + var assistant = new ViewModels.AIAssistant(repo, serivce, changes); + var view = new AIAssistant() { DataContext = assistant }; + view.Show(owner); + } } } From 3db91ed5aee2c0ee53c5de90658d3dd5f51eddcf Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 13 Apr 2026 14:05:19 +0800 Subject: [PATCH 02/23] feature: supports to choose group and bookmark when cloning remote repository Signed-off-by: leo --- src/Resources/Locales/en_US.axaml | 2 ++ src/Resources/Locales/zh_CN.axaml | 2 ++ src/Resources/Locales/zh_TW.axaml | 2 ++ src/ViewModels/Clone.cs | 49 ++++++++++++++++++++++++++++++- src/Views/Clone.axaml | 42 ++++++++++++++++++++++++-- 5 files changed, 94 insertions(+), 3 deletions(-) diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index a14c66a16..469ac96b9 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -130,6 +130,8 @@ Clone Remote Repository Extra Parameters: Additional arguments to clone repository. Optional. + Bookmark: + Group: Local Name: Repository name. Optional. Parent Folder: diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index b8b926bfc..9ab93f57d 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -134,6 +134,8 @@ 克隆远程仓库 额外参数 : 其他克隆参数,选填。 + 书签 : + 自定义分组 : 本地仓库名 : 本地仓库目录的名字,选填。 父级目录 : diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index 4751416ea..dd35a9960 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -134,6 +134,8 @@ 複製 (clone) 遠端存放庫 額外參數: 其他複製參數,選填。 + 書籤: + 群組: 本機存放庫名稱: 本機存放庫目錄的名稱,選填。 上層目錄: diff --git a/src/ViewModels/Clone.cs b/src/ViewModels/Clone.cs index 84e712fdb..d6ec8109c 100644 --- a/src/ViewModels/Clone.cs +++ b/src/ViewModels/Clone.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.IO; using System.Threading.Tasks; @@ -45,6 +46,28 @@ public string Local set => SetProperty(ref _local, value); } + public List Groups + { + get; + } + + public RepositoryNode SelectedGroup + { + get => _selectedGroup; + set => SetProperty(ref _selectedGroup, value); + } + + public List Bookmarks + { + get; + } + + public int Bookmark + { + get => _bookmark; + set => SetProperty(ref _bookmark, value); + } + public string ExtraArgs { get => _extraArgs; @@ -61,6 +84,15 @@ public Clone(string pageId) { _pageId = pageId; + Groups = new List(); + CollectGroups(Groups, Preferences.Instance.RepositoryNodes); + if (Groups.Count > 0) + SelectedGroup = Groups[0]; + + Bookmarks = new List(); + for (var i = 0; i < Models.Bookmarks.Brushes.Length; i++) + Bookmarks.Add(i); + var activeWorkspace = Preferences.Instance.GetActiveWorkspace(); _parentFolder = activeWorkspace?.DefaultCloneDir; if (string.IsNullOrEmpty(ParentFolder)) @@ -134,7 +166,8 @@ public override async Task Sure() log.Complete(); - var node = Preferences.Instance.FindOrAddNodeByRepositoryPath(path, null, true); + var node = Preferences.Instance.FindOrAddNodeByRepositoryPath(path, _selectedGroup, true); + node.Bookmark = _bookmark; await node.UpdateStatusAsync(false, null); var launcher = App.GetLauncher(); @@ -153,6 +186,18 @@ public override async Task Sure() return true; } + private void CollectGroups(List outs, List collections) + { + foreach (var node in collections) + { + if (!node.IsRepository) + { + outs.Add(node); + CollectGroups(outs, node.SubNodes); + } + } + } + private string _pageId = string.Empty; private string _remote = string.Empty; private bool _useSSH = false; @@ -160,5 +205,7 @@ public override async Task Sure() private string _parentFolder = string.Empty; private string _local = string.Empty; private string _extraArgs = string.Empty; + private RepositoryNode _selectedGroup = null; + private int _bookmark = 0; } } diff --git a/src/Views/Clone.axaml b/src/Views/Clone.axaml index 79289ed2f..a501474be 100644 --- a/src/Views/Clone.axaml +++ b/src/Views/Clone.axaml @@ -4,6 +4,7 @@ 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.Clone" x:DataType="vm:Clone"> @@ -19,7 +20,7 @@ Text="{DynamicResource Text.Clone}"/> - + - + + + + + + + + + + + + + + + + + + + + From c1a5e986bfa26f33c3804530bde1b0923ec70259 Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 13 Apr 2026 15:16:22 +0800 Subject: [PATCH 03/23] refactor: rewrite `Open Local Repository` feature Signed-off-by: leo --- src/Resources/Locales/en_US.axaml | 4 + src/Resources/Locales/zh_CN.axaml | 4 + src/Resources/Locales/zh_TW.axaml | 4 + src/ViewModels/OpenLocalRepository.cs | 115 +++++++++++++++++++++++++ src/ViewModels/Welcome.cs | 26 +++--- src/Views/OpenLocalRepository.axaml | 77 +++++++++++++++++ src/Views/OpenLocalRepository.axaml.cs | 56 ++++++++++++ src/Views/WelcomeToolbar.axaml | 2 +- src/Views/WelcomeToolbar.axaml.cs | 56 ------------ 9 files changed, 274 insertions(+), 70 deletions(-) create mode 100644 src/ViewModels/OpenLocalRepository.cs create mode 100644 src/Views/OpenLocalRepository.axaml create mode 100644 src/Views/OpenLocalRepository.axaml.cs diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index 469ac96b9..cea5479ab 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -594,6 +594,10 @@ Open Data Storage Directory Open File Open in External Merge Tool + Open Local Repository + Bookmark: + Group: + Folder: Optional. Create New Tab Close Tab diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 9ab93f57d..05ac46fb0 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -598,6 +598,10 @@ 浏览应用数据目录 打开文件 使用外部对比工具查看 + 打开本地仓库 + 书签 : + 分组 : + 仓库位置 : 选填。 新建空白页 关闭标签页 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index dd35a9960..890a75602 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -598,6 +598,10 @@ 瀏覽程式資料目錄 開啟檔案 使用外部比對工具檢視 + 開啟本機存放庫 + 書籤: + 群組: + 存放庫位置: 選填。 新增分頁 關閉分頁 diff --git a/src/ViewModels/OpenLocalRepository.cs b/src/ViewModels/OpenLocalRepository.cs new file mode 100644 index 000000000..18fd9d6a3 --- /dev/null +++ b/src/ViewModels/OpenLocalRepository.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.IO; +using System.Threading.Tasks; + +namespace SourceGit.ViewModels +{ + public class OpenLocalRepository : Popup + { + [Required(ErrorMessage = "Repository folder is required")] + [CustomValidation(typeof(OpenLocalRepository), nameof(ValidateRepoPath))] + public string RepoPath + { + get => _repoPath; + set => SetProperty(ref _repoPath, value, true); + } + + public List Groups + { + get; + } + + public RepositoryNode Group + { + get => _group; + set => SetProperty(ref _group, value); + } + + public List Bookmarks + { + get; + } + + public int Bookmark + { + get => _bookmark; + set => SetProperty(ref _bookmark, value); + } + + public OpenLocalRepository(string pageId, RepositoryNode group) + { + _pageId = pageId; + _group = group; + + Groups = new List(); + CollectGroups(Groups, Preferences.Instance.RepositoryNodes); + if (Groups.Count > 0 && _group == null) + Group = Groups[0]; + + Bookmarks = new List(); + for (var i = 0; i < Models.Bookmarks.Brushes.Length; i++) + Bookmarks.Add(i); + } + + public static ValidationResult ValidateRepoPath(string folder, ValidationContext _) + { + if (!Directory.Exists(folder)) + return new ValidationResult("Given path can NOT be found"); + return ValidationResult.Success; + } + + public override async Task Sure() + { + var isBare = await new Commands.IsBareRepository(_repoPath).GetResultAsync(); + var repoRoot = _repoPath; + if (!isBare) + { + var test = await new Commands.QueryRepositoryRootPath(_repoPath).GetResultAsync(); + if (test.IsSuccess && !string.IsNullOrWhiteSpace(test.StdOut)) + { + repoRoot = test.StdOut.Trim(); + } + else + { + var launcher = App.GetLauncher(); + foreach (var page in launcher.Pages) + { + if (page.Node.Id.Equals(_pageId, StringComparison.Ordinal)) + { + page.Popup = new Init(page.Node.Id, _repoPath, _group, test.StdErr); + break; + } + } + + return false; + } + } + + var node = Preferences.Instance.FindOrAddNodeByRepositoryPath(repoRoot, _group, true); + node.Bookmark = _bookmark; + await node.UpdateStatusAsync(false, null); + Welcome.Instance.Refresh(); + node.Open(); + return true; + } + + private void CollectGroups(List outs, List collections) + { + foreach (var node in collections) + { + if (!node.IsRepository) + { + outs.Add(node); + CollectGroups(outs, node.SubNodes); + } + } + } + + private string _pageId = string.Empty; + private string _repoPath = string.Empty; + private RepositoryNode _group = null; + private int _bookmark = 0; + } +} diff --git a/src/ViewModels/Welcome.cs b/src/ViewModels/Welcome.cs index 66bde74b6..49539292d 100644 --- a/src/ViewModels/Welcome.cs +++ b/src/ViewModels/Welcome.cs @@ -128,19 +128,6 @@ public async Task GetRepositoryRootAsync(string path) return rs.StdOut.Trim(); } - public void InitRepository(string path, RepositoryNode parent, string reason) - { - if (!Preferences.Instance.IsGitConfigured()) - { - Models.Notification.Send(null, App.Text("NotConfigured"), true); - return; - } - - var activePage = App.GetLauncher().ActivePage; - if (activePage != null && activePage.CanCreatePopup()) - activePage.Popup = new Init(activePage.Node.Id, path, parent, reason); - } - public async Task AddRepositoryAsync(string path, RepositoryNode parent, bool moveNode, bool open) { var node = Preferences.Instance.FindOrAddNodeByRepositoryPath(path, parent, moveNode); @@ -163,6 +150,19 @@ public void Clone() activePage.Popup = new Clone(activePage.Node.Id); } + public void OpenLocalRepository() + { + if (!Preferences.Instance.IsGitConfigured()) + { + Models.Notification.Send(null, App.Text("NotConfigured"), true); + return; + } + + var activePage = App.GetLauncher().ActivePage; + if (activePage != null && activePage.CanCreatePopup()) + activePage.Popup = new OpenLocalRepository(activePage.Node.Id, null); + } + public void OpenTerminal() { if (!Preferences.Instance.IsGitConfigured()) diff --git a/src/Views/OpenLocalRepository.axaml b/src/Views/OpenLocalRepository.axaml new file mode 100644 index 000000000..45dbfdf43 --- /dev/null +++ b/src/Views/OpenLocalRepository.axaml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Views/OpenLocalRepository.axaml.cs b/src/Views/OpenLocalRepository.axaml.cs new file mode 100644 index 000000000..151662c2c --- /dev/null +++ b/src/Views/OpenLocalRepository.axaml.cs @@ -0,0 +1,56 @@ +using System; +using System.IO; + +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Platform.Storage; + +namespace SourceGit.Views +{ + public partial class OpenLocalRepository : UserControl + { + public OpenLocalRepository() + { + InitializeComponent(); + } + + private async void OnSelectRepositoryFolder(object _1, RoutedEventArgs e) + { + if (DataContext is not ViewModels.OpenLocalRepository vm) + return; + + var topLevel = TopLevel.GetTopLevel(this); + if (topLevel == null) + return; + + var preference = ViewModels.Preferences.Instance; + var workspace = preference.GetActiveWorkspace(); + var initDir = workspace.DefaultCloneDir; + if (string.IsNullOrEmpty(initDir) || !Directory.Exists(initDir)) + initDir = preference.GitDefaultCloneDir; + + var options = new FolderPickerOpenOptions() { AllowMultiple = false }; + if (Directory.Exists(initDir)) + { + var folder = await topLevel.StorageProvider.TryGetFolderFromPathAsync(initDir); + options.SuggestedStartLocation = folder; + } + + try + { + var selected = await topLevel.StorageProvider.OpenFolderPickerAsync(options); + if (selected.Count == 1) + { + var folder = selected[0]; + vm.RepoPath = folder is { Path: { IsAbsoluteUri: true } path } ? path.LocalPath : folder?.Path.ToString(); + } + } + catch (Exception exception) + { + Models.Notification.Send(null, $"Failed to open repository: {exception.Message}", true); + } + + e.Handled = true; + } + } +} diff --git a/src/Views/WelcomeToolbar.axaml b/src/Views/WelcomeToolbar.axaml index c218058c9..0114c3d36 100644 --- a/src/Views/WelcomeToolbar.axaml +++ b/src/Views/WelcomeToolbar.axaml @@ -19,7 +19,7 @@ - diff --git a/src/Views/WelcomeToolbar.axaml.cs b/src/Views/WelcomeToolbar.axaml.cs index d949c0955..19894c4a8 100644 --- a/src/Views/WelcomeToolbar.axaml.cs +++ b/src/Views/WelcomeToolbar.axaml.cs @@ -1,9 +1,4 @@ -using System; -using System.IO; - using Avalonia.Controls; -using Avalonia.Interactivity; -using Avalonia.Platform.Storage; namespace SourceGit.Views { @@ -13,56 +8,5 @@ public WelcomeToolbar() { InitializeComponent(); } - - private async void OpenLocalRepository(object _1, RoutedEventArgs e) - { - var activePage = App.GetLauncher().ActivePage; - if (activePage == null || !activePage.CanCreatePopup()) - return; - - var topLevel = TopLevel.GetTopLevel(this); - if (topLevel == null) - return; - - var preference = ViewModels.Preferences.Instance; - var workspace = preference.GetActiveWorkspace(); - var initDir = workspace.DefaultCloneDir; - if (string.IsNullOrEmpty(initDir) || !Directory.Exists(initDir)) - initDir = preference.GitDefaultCloneDir; - - var options = new FolderPickerOpenOptions() { AllowMultiple = false }; - if (Directory.Exists(initDir)) - { - var folder = await topLevel.StorageProvider.TryGetFolderFromPathAsync(initDir); - options.SuggestedStartLocation = folder; - } - - try - { - var selected = await topLevel.StorageProvider.OpenFolderPickerAsync(options); - if (selected.Count == 1) - { - var folder = selected[0]; - var folderPath = folder is { Path: { IsAbsoluteUri: true } path } ? path.LocalPath : folder?.Path.ToString(); - var repoPath = await ViewModels.Welcome.Instance.GetRepositoryRootAsync(folderPath); - if (!string.IsNullOrEmpty(repoPath)) - { - await ViewModels.Welcome.Instance.AddRepositoryAsync(repoPath, null, false, true); - ViewModels.Welcome.Instance.Refresh(); - } - else if (Directory.Exists(folderPath)) - { - var test = await new Commands.QueryRepositoryRootPath(folderPath).GetResultAsync(); - ViewModels.Welcome.Instance.InitRepository(folderPath, null, test.StdErr); - } - } - } - catch (Exception exception) - { - Models.Notification.Send(null, $"Failed to open repository: {exception.Message}", true); - } - - e.Handled = true; - } } } From d6f45cca7d4d0177bd9a91a0f9d1ac139b9c9491 Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 13 Apr 2026 15:17:43 +0800 Subject: [PATCH 04/23] fix: bad condition to check if auto-fetch is enabled (#2257) Signed-off-by: leo --- src/ViewModels/Repository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index 7f91241f5..6b748f357 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -1887,7 +1887,7 @@ private async Task AutoFetchOnUIThread() try { - if (Preferences.Instance.EnableAutoFetch || !CanCreatePopup()) + if (!Preferences.Instance.EnableAutoFetch || !CanCreatePopup()) { _lastFetchTime = DateTime.Now; return; From 6eaf636b2335f1083e7fbfbe5141700472fe426c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 13 Apr 2026 07:20:39 +0000 Subject: [PATCH 05/23] doc: Update translation status and sort locale files --- TRANSLATION.md | 100 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 89 insertions(+), 11 deletions(-) diff --git a/TRANSLATION.md b/TRANSLATION.md index 813b763c6..6ed0b42b7 100644 --- a/TRANSLATION.md +++ b/TRANSLATION.md @@ -6,7 +6,7 @@ This document shows the translation status of each locale file in the repository ### ![en_US](https://img.shields.io/badge/en__US-%E2%88%9A-brightgreen) -### ![de__DE](https://img.shields.io/badge/de__DE-97.53%25-yellow) +### ![de__DE](https://img.shields.io/badge/de__DE-96.94%25-yellow)
Missing keys in de_DE.axaml @@ -16,6 +16,8 @@ This document shows the translation status of each locale file in the repository - Text.CheckoutBranchFromStash - Text.CheckoutBranchFromStash.Branch - Text.CheckoutBranchFromStash.Stash +- Text.Clone.Bookmark +- Text.Clone.Group - Text.CommandPalette.Branches - Text.CommandPalette.BranchesAndTags - Text.CommandPalette.RepositoryActions @@ -28,6 +30,10 @@ This document shows the translation status of each locale file in the repository - Text.Hotkeys.Repo.GoToChild - Text.Init.CommandTip - Text.Init.ErrorMessageTip +- Text.OpenLocalRepository +- Text.OpenLocalRepository.Bookmark +- Text.OpenLocalRepository.Group +- Text.OpenLocalRepository.Path - Text.Preferences.AI.AdditionalPrompt - Text.Preferences.General.Use24Hours - Text.StashCM.ApplyFileChanges @@ -38,9 +44,21 @@ This document shows the translation status of each locale file in the repository
-### ![es__ES](https://img.shields.io/badge/es__ES-%E2%88%9A-brightgreen) +### ![es__ES](https://img.shields.io/badge/es__ES-99.39%25-yellow) -### ![fr__FR](https://img.shields.io/badge/fr__FR-91.37%25-yellow) +
+Missing keys in es_ES.axaml + +- Text.Clone.Bookmark +- Text.Clone.Group +- Text.OpenLocalRepository +- Text.OpenLocalRepository.Bookmark +- Text.OpenLocalRepository.Group +- Text.OpenLocalRepository.Path + +
+ +### ![fr__FR](https://img.shields.io/badge/fr__FR-90.81%25-yellow)
Missing keys in fr_FR.axaml @@ -60,6 +78,8 @@ This document shows the translation status of each locale file in the repository - Text.CheckoutBranchFromStash - Text.CheckoutBranchFromStash.Branch - Text.CheckoutBranchFromStash.Stash +- Text.Clone.Bookmark +- Text.Clone.Group - Text.CommandPalette.Branches - Text.CommandPalette.BranchesAndTags - Text.CommandPalette.RepositoryActions @@ -101,6 +121,10 @@ This document shows the translation status of each locale file in the repository - Text.MergeConflictEditor.Undo - Text.No - Text.OpenFile +- Text.OpenLocalRepository +- Text.OpenLocalRepository.Bookmark +- Text.OpenLocalRepository.Group +- Text.OpenLocalRepository.Path - Text.PageTabBar.Tab.MoveToWorkspace - Text.PageTabBar.Tab.Refresh - Text.Preferences.AI.AdditionalPrompt @@ -132,7 +156,7 @@ This document shows the translation status of each locale file in the repository
-### ![id__ID](https://img.shields.io/badge/id__ID-89.21%25-yellow) +### ![id__ID](https://img.shields.io/badge/id__ID-88.66%25-yellow)
Missing keys in id_ID.axaml @@ -156,6 +180,8 @@ This document shows the translation status of each locale file in the repository - Text.CheckoutBranchFromStash - Text.CheckoutBranchFromStash.Branch - Text.CheckoutBranchFromStash.Stash +- Text.Clone.Bookmark +- Text.Clone.Group - Text.CommandPalette.Branches - Text.CommandPalette.BranchesAndTags - Text.CommandPalette.RepositoryActions @@ -212,6 +238,10 @@ This document shows the translation status of each locale file in the repository - Text.Open - Text.Open.SystemDefaultEditor - Text.OpenFile +- Text.OpenLocalRepository +- Text.OpenLocalRepository.Bookmark +- Text.OpenLocalRepository.Group +- Text.OpenLocalRepository.Path - Text.PageTabBar.Tab.MoveToWorkspace - Text.PageTabBar.Tab.Refresh - Text.Preferences.AI.AdditionalPrompt @@ -245,7 +275,7 @@ This document shows the translation status of each locale file in the repository
-### ![it__IT](https://img.shields.io/badge/it__IT-96.92%25-yellow) +### ![it__IT](https://img.shields.io/badge/it__IT-96.32%25-yellow)
Missing keys in it_IT.axaml @@ -256,6 +286,8 @@ This document shows the translation status of each locale file in the repository - Text.CheckoutBranchFromStash - Text.CheckoutBranchFromStash.Branch - Text.CheckoutBranchFromStash.Stash +- Text.Clone.Bookmark +- Text.Clone.Group - Text.CommandPalette.Branches - Text.CommandPalette.BranchesAndTags - Text.CommandPalette.RepositoryActions @@ -271,6 +303,10 @@ This document shows the translation status of each locale file in the repository - Text.Hotkeys.Repo.GoToParent - Text.Init.CommandTip - Text.Init.ErrorMessageTip +- Text.OpenLocalRepository +- Text.OpenLocalRepository.Bookmark +- Text.OpenLocalRepository.Group +- Text.OpenLocalRepository.Path - Text.Preferences.AI.AdditionalPrompt - Text.Preferences.General.Use24Hours - Text.SelfUpdate.CurrentVersion @@ -283,7 +319,7 @@ This document shows the translation status of each locale file in the repository
-### ![ja__JP](https://img.shields.io/badge/ja__JP-97.84%25-yellow) +### ![ja__JP](https://img.shields.io/badge/ja__JP-97.24%25-yellow)
Missing keys in ja_JP.axaml @@ -293,6 +329,8 @@ This document shows the translation status of each locale file in the repository - Text.CheckoutBranchFromStash - Text.CheckoutBranchFromStash.Branch - Text.CheckoutBranchFromStash.Stash +- Text.Clone.Bookmark +- Text.Clone.Group - Text.CommandPalette.Branches - Text.CommandPalette.BranchesAndTags - Text.CommandPalette.RepositoryActions @@ -303,6 +341,10 @@ This document shows the translation status of each locale file in the repository - Text.Hotkeys.Repo.CreateBranch - Text.Init.CommandTip - Text.Init.ErrorMessageTip +- Text.OpenLocalRepository +- Text.OpenLocalRepository.Bookmark +- Text.OpenLocalRepository.Group +- Text.OpenLocalRepository.Path - Text.Preferences.AI.AdditionalPrompt - Text.Preferences.General.Use24Hours - Text.StashCM.Branch @@ -312,7 +354,7 @@ This document shows the translation status of each locale file in the repository
-### ![ko__KR](https://img.shields.io/badge/ko__KR-89.52%25-yellow) +### ![ko__KR](https://img.shields.io/badge/ko__KR-88.97%25-yellow)
Missing keys in ko_KR.axaml @@ -336,6 +378,8 @@ This document shows the translation status of each locale file in the repository - Text.CheckoutBranchFromStash - Text.CheckoutBranchFromStash.Branch - Text.CheckoutBranchFromStash.Stash +- Text.Clone.Bookmark +- Text.Clone.Group - Text.CommandPalette.Branches - Text.CommandPalette.BranchesAndTags - Text.CommandPalette.RepositoryActions @@ -387,6 +431,10 @@ This document shows the translation status of each locale file in the repository - Text.Open - Text.Open.SystemDefaultEditor - Text.OpenFile +- Text.OpenLocalRepository +- Text.OpenLocalRepository.Bookmark +- Text.OpenLocalRepository.Group +- Text.OpenLocalRepository.Path - Text.PageTabBar.Tab.MoveToWorkspace - Text.PageTabBar.Tab.Refresh - Text.Preferences.AI.AdditionalPrompt @@ -422,7 +470,7 @@ This document shows the translation status of each locale file in the repository
-### ![pt__BR](https://img.shields.io/badge/pt__BR-67.73%25-red) +### ![pt__BR](https://img.shields.io/badge/pt__BR-67.31%25-red)
Missing keys in pt_BR.axaml @@ -449,6 +497,8 @@ This document shows the translation status of each locale file in the repository - Text.CheckoutBranchFromStash - Text.CheckoutBranchFromStash.Branch - Text.CheckoutBranchFromStash.Stash +- Text.Clone.Bookmark +- Text.Clone.Group - Text.Clone.RecurseSubmodules - Text.CommandPalette.Branches - Text.CommandPalette.BranchesAndTags @@ -610,6 +660,10 @@ This document shows the translation status of each locale file in the repository - Text.Open - Text.Open.SystemDefaultEditor - Text.OpenFile +- Text.OpenLocalRepository +- Text.OpenLocalRepository.Bookmark +- Text.OpenLocalRepository.Group +- Text.OpenLocalRepository.Path - Text.PageTabBar.Tab.MoveToWorkspace - Text.PageTabBar.Tab.Refresh - Text.Preferences.AI.AdditionalPrompt @@ -744,9 +798,21 @@ This document shows the translation status of each locale file in the repository
-### ![ru__RU](https://img.shields.io/badge/ru__RU-%E2%88%9A-brightgreen) +### ![ru__RU](https://img.shields.io/badge/ru__RU-99.39%25-yellow) + +
+Missing keys in ru_RU.axaml + +- Text.Clone.Bookmark +- Text.Clone.Group +- Text.OpenLocalRepository +- Text.OpenLocalRepository.Bookmark +- Text.OpenLocalRepository.Group +- Text.OpenLocalRepository.Path + +
-### ![ta__IN](https://img.shields.io/badge/ta__IN-69.78%25-red) +### ![ta__IN](https://img.shields.io/badge/ta__IN-69.36%25-red)
Missing keys in ta_IN.axaml @@ -802,6 +868,8 @@ This document shows the translation status of each locale file in the repository - Text.CheckoutBranchFromStash - Text.CheckoutBranchFromStash.Branch - Text.CheckoutBranchFromStash.Stash +- Text.Clone.Bookmark +- Text.Clone.Group - Text.CommandPalette.Branches - Text.CommandPalette.BranchesAndTags - Text.CommandPalette.RepositoryActions @@ -937,6 +1005,10 @@ This document shows the translation status of each locale file in the repository - Text.Open - Text.Open.SystemDefaultEditor - Text.OpenFile +- Text.OpenLocalRepository +- Text.OpenLocalRepository.Bookmark +- Text.OpenLocalRepository.Group +- Text.OpenLocalRepository.Path - Text.PageTabBar.Tab.MoveToWorkspace - Text.PageTabBar.Tab.Refresh - Text.Preferences.AI.AdditionalPrompt @@ -1048,7 +1120,7 @@ This document shows the translation status of each locale file in the repository
-### ![uk__UA](https://img.shields.io/badge/uk__UA-70.61%25-red) +### ![uk__UA](https://img.shields.io/badge/uk__UA-70.17%25-red)
Missing keys in uk_UA.axaml @@ -1104,6 +1176,8 @@ This document shows the translation status of each locale file in the repository - Text.CheckoutBranchFromStash - Text.CheckoutBranchFromStash.Branch - Text.CheckoutBranchFromStash.Stash +- Text.Clone.Bookmark +- Text.Clone.Group - Text.CommandPalette.Branches - Text.CommandPalette.BranchesAndTags - Text.CommandPalette.RepositoryActions @@ -1235,6 +1309,10 @@ This document shows the translation status of each locale file in the repository - Text.Open - Text.Open.SystemDefaultEditor - Text.OpenFile +- Text.OpenLocalRepository +- Text.OpenLocalRepository.Bookmark +- Text.OpenLocalRepository.Group +- Text.OpenLocalRepository.Path - Text.PageTabBar.Tab.MoveToWorkspace - Text.PageTabBar.Tab.Refresh - Text.Preferences.AI.AdditionalPrompt From 6f58f479c2a3c499b54e373c864a6917eb39344e Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 13 Apr 2026 15:42:49 +0800 Subject: [PATCH 06/23] =?UTF-8?q?feature:=20add=20hotkey=20`Ctrl+Shift+O/?= =?UTF-8?q?=E2=8C=98+=E2=87=A7+O`=20to=20open=20local=20repository=20(#225?= =?UTF-8?q?6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: leo --- src/Resources/Locales/en_US.axaml | 1 + src/Resources/Locales/zh_CN.axaml | 1 + src/Resources/Locales/zh_TW.axaml | 1 + src/Views/Hotkeys.axaml | 21 ++++++++++++--------- src/Views/Launcher.axaml.cs | 10 ++++++++++ src/Views/WelcomeToolbar.axaml | 9 ++++++++- 6 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index cea5479ab..b009e0e6d 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -501,6 +501,7 @@ Go to next tab Go to previous tab Create new tab + Open local repository Open Preferences dialog Show workspace dropdown menu Switch active tab diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 05ac46fb0..59ba70477 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -505,6 +505,7 @@ 切换到下一个页面 切换到上一个页面 新建页面 + 打开本地仓库 打开偏好设置面板 显示工作区下拉菜单 切换显示页面 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index 890a75602..ea1146d30 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -505,6 +505,7 @@ 切換到下一個頁面 切換到上一個頁面 新增頁面 + 開啟本機存放庫 開啟偏好設定面板 顯示工作區的下拉式選單 切換目前頁面 diff --git a/src/Views/Hotkeys.axaml b/src/Views/Hotkeys.axaml index 855e205b5..c2f0561eb 100644 --- a/src/Views/Hotkeys.axaml +++ b/src/Views/Hotkeys.axaml @@ -45,7 +45,7 @@ FontSize="{Binding Source={x:Static vm:Preferences.Instance}, Path=DefaultFontSize, Converter={x:Static c:DoubleConverters.Increase}}" Margin="0,0,0,8"/> - + @@ -64,17 +64,20 @@ - - + + - - + + - - + + - - + + + + + - From f8e8dcab56d2fda14e18ab59236179e49eb10533 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 13 Apr 2026 07:43:11 +0000 Subject: [PATCH 07/23] doc: Update translation status and sort locale files --- TRANSLATION.md | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/TRANSLATION.md b/TRANSLATION.md index 6ed0b42b7..81c5b158a 100644 --- a/TRANSLATION.md +++ b/TRANSLATION.md @@ -6,7 +6,7 @@ This document shows the translation status of each locale file in the repository ### ![en_US](https://img.shields.io/badge/en__US-%E2%88%9A-brightgreen) -### ![de__DE](https://img.shields.io/badge/de__DE-96.94%25-yellow) +### ![de__DE](https://img.shields.io/badge/de__DE-96.84%25-yellow)
Missing keys in de_DE.axaml @@ -26,6 +26,7 @@ This document shows the translation status of each locale file in the repository - Text.ConfirmEmptyCommit.StageSelectedThenCommit - Text.Discard.IncludeModified - Text.GotoRevisionSelector +- Text.Hotkeys.Global.OpenLocalRepository - Text.Hotkeys.Repo.CreateBranch - Text.Hotkeys.Repo.GoToChild - Text.Init.CommandTip @@ -44,13 +45,14 @@ This document shows the translation status of each locale file in the repository
-### ![es__ES](https://img.shields.io/badge/es__ES-99.39%25-yellow) +### ![es__ES](https://img.shields.io/badge/es__ES-99.29%25-yellow)
Missing keys in es_ES.axaml - Text.Clone.Bookmark - Text.Clone.Group +- Text.Hotkeys.Global.OpenLocalRepository - Text.OpenLocalRepository - Text.OpenLocalRepository.Bookmark - Text.OpenLocalRepository.Group @@ -58,7 +60,7 @@ This document shows the translation status of each locale file in the repository
-### ![fr__FR](https://img.shields.io/badge/fr__FR-90.81%25-yellow) +### ![fr__FR](https://img.shields.io/badge/fr__FR-90.71%25-yellow)
Missing keys in fr_FR.axaml @@ -96,6 +98,7 @@ This document shows the translation status of each locale file in the repository - Text.GotoRevisionSelector - Text.Histories.Header.DateTime - Text.Histories.ShowColumns +- Text.Hotkeys.Global.OpenLocalRepository - Text.Hotkeys.Global.ShowWorkspaceDropdownMenu - Text.Hotkeys.Global.Zoom - Text.Hotkeys.Repo.CreateBranch @@ -156,7 +159,7 @@ This document shows the translation status of each locale file in the repository
-### ![id__ID](https://img.shields.io/badge/id__ID-88.66%25-yellow) +### ![id__ID](https://img.shields.io/badge/id__ID-88.57%25-yellow)
Missing keys in id_ID.axaml @@ -208,6 +211,7 @@ This document shows the translation status of each locale file in the repository - Text.GotoRevisionSelector - Text.Histories.Header.DateTime - Text.Histories.ShowColumns +- Text.Hotkeys.Global.OpenLocalRepository - Text.Hotkeys.Global.ShowWorkspaceDropdownMenu - Text.Hotkeys.Global.Zoom - Text.Hotkeys.Repo.CreateBranch @@ -275,7 +279,7 @@ This document shows the translation status of each locale file in the repository
-### ![it__IT](https://img.shields.io/badge/it__IT-96.32%25-yellow) +### ![it__IT](https://img.shields.io/badge/it__IT-96.22%25-yellow)
Missing keys in it_IT.axaml @@ -298,6 +302,7 @@ This document shows the translation status of each locale file in the repository - Text.GotoRevisionSelector - Text.Histories.Header.DateTime - Text.Histories.ShowColumns +- Text.Hotkeys.Global.OpenLocalRepository - Text.Hotkeys.Repo.CreateBranch - Text.Hotkeys.Repo.GoToChild - Text.Hotkeys.Repo.GoToParent @@ -319,7 +324,7 @@ This document shows the translation status of each locale file in the repository
-### ![ja__JP](https://img.shields.io/badge/ja__JP-97.24%25-yellow) +### ![ja__JP](https://img.shields.io/badge/ja__JP-97.14%25-yellow)
Missing keys in ja_JP.axaml @@ -338,6 +343,7 @@ This document shows the translation status of each locale file in the repository - Text.ConfirmEmptyCommit.StageSelectedThenCommit - Text.DealWithLocalChanges.DoNothing - Text.Discard.IncludeModified +- Text.Hotkeys.Global.OpenLocalRepository - Text.Hotkeys.Repo.CreateBranch - Text.Init.CommandTip - Text.Init.ErrorMessageTip @@ -354,7 +360,7 @@ This document shows the translation status of each locale file in the repository
-### ![ko__KR](https://img.shields.io/badge/ko__KR-88.97%25-yellow) +### ![ko__KR](https://img.shields.io/badge/ko__KR-88.88%25-yellow)
Missing keys in ko_KR.axaml @@ -401,6 +407,7 @@ This document shows the translation status of each locale file in the repository - Text.GotoRevisionSelector - Text.Histories.Header.DateTime - Text.Histories.ShowColumns +- Text.Hotkeys.Global.OpenLocalRepository - Text.Hotkeys.Global.ShowWorkspaceDropdownMenu - Text.Hotkeys.Global.Zoom - Text.Hotkeys.Repo.CreateBranch @@ -470,7 +477,7 @@ This document shows the translation status of each locale file in the repository
-### ![pt__BR](https://img.shields.io/badge/pt__BR-67.31%25-red) +### ![pt__BR](https://img.shields.io/badge/pt__BR-67.24%25-red)
Missing keys in pt_BR.axaml @@ -612,6 +619,7 @@ This document shows the translation status of each locale file in the repository - Text.Histories.Header.DateTime - Text.Histories.ShowColumns - Text.Hotkeys.Global.Clone +- Text.Hotkeys.Global.OpenLocalRepository - Text.Hotkeys.Global.ShowWorkspaceDropdownMenu - Text.Hotkeys.Global.SwitchTab - Text.Hotkeys.Global.Zoom @@ -798,13 +806,14 @@ This document shows the translation status of each locale file in the repository
-### ![ru__RU](https://img.shields.io/badge/ru__RU-99.39%25-yellow) +### ![ru__RU](https://img.shields.io/badge/ru__RU-99.29%25-yellow)
Missing keys in ru_RU.axaml - Text.Clone.Bookmark - Text.Clone.Group +- Text.Hotkeys.Global.OpenLocalRepository - Text.OpenLocalRepository - Text.OpenLocalRepository.Bookmark - Text.OpenLocalRepository.Group @@ -812,7 +821,7 @@ This document shows the translation status of each locale file in the repository
-### ![ta__IN](https://img.shields.io/badge/ta__IN-69.36%25-red) +### ![ta__IN](https://img.shields.io/badge/ta__IN-69.29%25-red)
Missing keys in ta_IN.axaml @@ -966,6 +975,7 @@ This document shows the translation status of each locale file in the repository - Text.GotoRevisionSelector - Text.Histories.Header.DateTime - Text.Histories.ShowColumns +- Text.Hotkeys.Global.OpenLocalRepository - Text.Hotkeys.Global.ShowWorkspaceDropdownMenu - Text.Hotkeys.Global.SwitchTab - Text.Hotkeys.Global.Zoom @@ -1120,7 +1130,7 @@ This document shows the translation status of each locale file in the repository
-### ![uk__UA](https://img.shields.io/badge/uk__UA-70.17%25-red) +### ![uk__UA](https://img.shields.io/badge/uk__UA-70.10%25-red)
Missing keys in uk_UA.axaml @@ -1270,6 +1280,7 @@ This document shows the translation status of each locale file in the repository - Text.GotoRevisionSelector - Text.Histories.Header.DateTime - Text.Histories.ShowColumns +- Text.Hotkeys.Global.OpenLocalRepository - Text.Hotkeys.Global.ShowWorkspaceDropdownMenu - Text.Hotkeys.Global.SwitchTab - Text.Hotkeys.Global.Zoom From c4c75c32c7f019e823300462f7fdb10cbc2ddd01 Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 13 Apr 2026 17:28:55 +0800 Subject: [PATCH 08/23] revert: use blank `User-Agent` header when communicating with open-ai compatible service This reverts commit 26ab0a7529ff502e375baa42a6c7322c22d8b428. Signed-off-by: leo --- src/AI/Service.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AI/Service.cs b/src/AI/Service.cs index f7eb25e62..e36bc15bc 100644 --- a/src/AI/Service.cs +++ b/src/AI/Service.cs @@ -84,8 +84,8 @@ private OpenAIClient GetOpenAIClient() { var credential = new ApiKeyCredential(ReadApiKeyFromEnv ? Environment.GetEnvironmentVariable(ApiKey) : ApiKey); return Server.Contains("openai.azure.com/", StringComparison.Ordinal) - ? new AzureOpenAIClient(new Uri(Server), credential, new AzureOpenAIClientOptions() { UserAgentApplicationId = string.Empty }) - : new OpenAIClient(credential, new() { Endpoint = new Uri(Server), UserAgentApplicationId = string.Empty }); + ? new AzureOpenAIClient(new Uri(Server), credential) + : new OpenAIClient(credential, new() { Endpoint = new Uri(Server) }); } private string _name = string.Empty; From b7796f19efcdef7d0d9e8cde3950bc101c194425 Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 13 Apr 2026 18:04:08 +0800 Subject: [PATCH 09/23] feature: show a checked icon when sha was copied Signed-off-by: leo --- src/Views/CommitBaseInfo.axaml | 5 +++- src/Views/CommitBaseInfo.axaml.cs | 38 +++++++++++++++++++++++++++++++ src/Views/CommitTimeTextBlock.cs | 10 +++----- 3 files changed, 45 insertions(+), 8 deletions(-) diff --git a/src/Views/CommitBaseInfo.axaml b/src/Views/CommitBaseInfo.axaml index 7af9d59bd..67c1c7b60 100644 --- a/src/Views/CommitBaseInfo.axaml +++ b/src/Views/CommitBaseInfo.axaml @@ -62,7 +62,10 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Views/SubmoduleRevisionCompare.axaml.cs b/src/Views/SubmoduleRevisionCompare.axaml.cs new file mode 100644 index 000000000..3ee7cee64 --- /dev/null +++ b/src/Views/SubmoduleRevisionCompare.axaml.cs @@ -0,0 +1,190 @@ +using System; +using System.IO; +using System.Text; + +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Platform.Storage; + +namespace SourceGit.Views +{ + public partial class SubmoduleRevisionCompare : ChromelessWindow + { + public SubmoduleRevisionCompare() + { + InitializeComponent(); + } + + private void OnChangeContextRequested(object sender, ContextRequestedEventArgs e) + { + if (DataContext is ViewModels.SubmoduleRevisionCompare { SelectedChanges: { Count: > 0 } selected } vm && + sender is ChangeCollectionView view) + { + var menu = new ContextMenu(); + + var patch = new MenuItem(); + patch.Header = App.Text("FileCM.SaveAsPatch"); + patch.Icon = this.CreateMenuIcon("Icons.Save"); + patch.Click += async (_, e) => + { + var storageProvider = this.StorageProvider; + if (storageProvider == null) + return; + + var options = new FilePickerSaveOptions(); + options.Title = App.Text("FileCM.SaveAsPatch"); + options.DefaultExtension = ".patch"; + options.FileTypeChoices = [new FilePickerFileType("Patch File") { Patterns = ["*.patch"] }]; + + try + { + var storageFile = await storageProvider.SaveFilePickerAsync(options); + if (storageFile != null) + { + var saveTo = storageFile.Path.LocalPath; + var succ = await vm.SaveChangesAsPatchAsync(selected, saveTo); + if (succ) + await new Alert().ShowAsync(this, "Save patch successfully.", false); + } + } + catch (Exception exception) + { + await new Alert().ShowAsync(this, $"Failed to save as patch: {exception.Message}", true); + } + + e.Handled = true; + }; + + if (selected.Count == 1) + { + var change = selected[0]; + var openWithMerger = new MenuItem(); + openWithMerger.Header = App.Text("OpenInExternalMergeTool"); + openWithMerger.Icon = this.CreateMenuIcon("Icons.OpenWith"); + openWithMerger.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+D" : "Ctrl+Shift+D"; + openWithMerger.Click += (_, ev) => + { + vm.OpenInExternalDiffTool(change); + ev.Handled = true; + }; + menu.Items.Add(openWithMerger); + + if (change.Index != Models.ChangeState.Deleted) + { + var full = vm.GetAbsPath(change.Path); + var explore = new MenuItem(); + explore.Header = App.Text("RevealFile"); + explore.Icon = this.CreateMenuIcon("Icons.Explore"); + explore.IsEnabled = File.Exists(full); + explore.Click += (_, ev) => + { + Native.OS.OpenInFileManager(full); + ev.Handled = true; + }; + menu.Items.Add(explore); + } + + menu.Items.Add(new MenuItem() { Header = "-" }); + menu.Items.Add(patch); + + var copyPath = new MenuItem(); + copyPath.Header = App.Text("CopyPath"); + copyPath.Icon = this.CreateMenuIcon("Icons.Copy"); + copyPath.Tag = OperatingSystem.IsMacOS() ? "⌘+C" : "Ctrl+C"; + copyPath.Click += async (_, ev) => + { + await this.CopyTextAsync(change.Path); + ev.Handled = true; + }; + + var copyFullPath = new MenuItem(); + copyFullPath.Header = App.Text("CopyFullPath"); + copyFullPath.Icon = this.CreateMenuIcon("Icons.Copy"); + copyFullPath.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+C" : "Ctrl+Shift+C"; + copyFullPath.Click += async (_, ev) => + { + await this.CopyTextAsync(vm.GetAbsPath(change.Path)); + ev.Handled = true; + }; + + menu.Items.Add(new MenuItem() { Header = "-" }); + menu.Items.Add(copyPath); + menu.Items.Add(copyFullPath); + } + else + { + menu.Items.Add(patch); + + var copyPath = new MenuItem(); + copyPath.Header = App.Text("CopyPath"); + copyPath.Icon = this.CreateMenuIcon("Icons.Copy"); + copyPath.Tag = OperatingSystem.IsMacOS() ? "⌘+C" : "Ctrl+C"; + copyPath.Click += async (_, ev) => + { + var builder = new StringBuilder(); + foreach (var c in selected) + builder.AppendLine(c.Path); + + await this.CopyTextAsync(builder.ToString()); + ev.Handled = true; + }; + + var copyFullPath = new MenuItem(); + copyFullPath.Header = App.Text("CopyFullPath"); + copyFullPath.Icon = this.CreateMenuIcon("Icons.Copy"); + copyFullPath.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+C" : "Ctrl+Shift+C"; + copyFullPath.Click += async (_, ev) => + { + var builder = new StringBuilder(); + foreach (var c in selected) + builder.AppendLine(vm.GetAbsPath(c.Path)); + + await this.CopyTextAsync(builder.ToString()); + ev.Handled = true; + }; + + menu.Items.Add(new MenuItem() { Header = "-" }); + menu.Items.Add(copyPath); + menu.Items.Add(copyFullPath); + } + + menu.Open(view); + } + + e.Handled = true; + } + + private async void OnChangeCollectionViewKeyDown(object sender, KeyEventArgs e) + { + if (DataContext is not ViewModels.SubmoduleRevisionCompare vm) + return; + + if (sender is not ChangeCollectionView { SelectedChanges: { Count: > 0 } selectedChanges }) + return; + + var cmdKey = OperatingSystem.IsMacOS() ? KeyModifiers.Meta : KeyModifiers.Control; + if (e.Key == Key.C && e.KeyModifiers.HasFlag(cmdKey)) + { + var builder = new StringBuilder(); + var copyAbsPath = e.KeyModifiers.HasFlag(KeyModifiers.Shift); + if (selectedChanges.Count == 1) + { + builder.Append(copyAbsPath ? vm.GetAbsPath(selectedChanges[0].Path) : selectedChanges[0].Path); + } + else + { + foreach (var c in selectedChanges) + builder.AppendLine(copyAbsPath ? vm.GetAbsPath(c.Path) : c.Path); + } + + await this.CopyTextAsync(builder.ToString()); + e.Handled = true; + } + else if (e.Key == Key.F && e.KeyModifiers == cmdKey) + { + ChangeSearchBox.Focus(); + e.Handled = true; + } + } + } +} From 87768e9d98acc97e43c565858eaed9da5b202fa6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 14 Apr 2026 12:15:24 +0000 Subject: [PATCH 18/23] doc: Update translation status and sort locale files --- TRANSLATION.md | 44 +++++++++++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/TRANSLATION.md b/TRANSLATION.md index 81c5b158a..75d830750 100644 --- a/TRANSLATION.md +++ b/TRANSLATION.md @@ -6,7 +6,7 @@ This document shows the translation status of each locale file in the repository ### ![en_US](https://img.shields.io/badge/en__US-%E2%88%9A-brightgreen) -### ![de__DE](https://img.shields.io/badge/de__DE-96.84%25-yellow) +### ![de__DE](https://img.shields.io/badge/de__DE-96.64%25-yellow)
Missing keys in de_DE.axaml @@ -39,13 +39,15 @@ This document shows the translation status of each locale file in the repository - Text.Preferences.General.Use24Hours - Text.StashCM.ApplyFileChanges - Text.StashCM.Branch +- Text.SubmoduleRevisionCompare +- Text.SubmoduleRevisionCompare.OpenDetails - Text.Worktree.Branch - Text.Worktree.Head - Text.Worktree.Path
-### ![es__ES](https://img.shields.io/badge/es__ES-99.29%25-yellow) +### ![es__ES](https://img.shields.io/badge/es__ES-99.08%25-yellow)
Missing keys in es_ES.axaml @@ -57,10 +59,12 @@ This document shows the translation status of each locale file in the repository - Text.OpenLocalRepository.Bookmark - Text.OpenLocalRepository.Group - Text.OpenLocalRepository.Path +- Text.SubmoduleRevisionCompare +- Text.SubmoduleRevisionCompare.OpenDetails
-### ![fr__FR](https://img.shields.io/badge/fr__FR-90.71%25-yellow) +### ![fr__FR](https://img.shields.io/badge/fr__FR-90.53%25-yellow)
Missing keys in fr_FR.axaml @@ -147,6 +151,8 @@ This document shows the translation status of each locale file in the repository - Text.SquashOrFixup.Into - Text.StashCM.ApplyFileChanges - Text.StashCM.Branch +- Text.SubmoduleRevisionCompare +- Text.SubmoduleRevisionCompare.OpenDetails - Text.TagCM.CompareTwo - Text.TagCM.CompareWith - Text.TagCM.CompareWithHead @@ -159,7 +165,7 @@ This document shows the translation status of each locale file in the repository
-### ![id__ID](https://img.shields.io/badge/id__ID-88.57%25-yellow) +### ![id__ID](https://img.shields.io/badge/id__ID-88.39%25-yellow)
Missing keys in id_ID.axaml @@ -267,6 +273,8 @@ This document shows the translation status of each locale file in the repository - Text.SquashOrFixup.Into - Text.StashCM.ApplyFileChanges - Text.StashCM.Branch +- Text.SubmoduleRevisionCompare +- Text.SubmoduleRevisionCompare.OpenDetails - Text.TagCM.CompareTwo - Text.TagCM.CompareWith - Text.TagCM.CompareWithHead @@ -279,7 +287,7 @@ This document shows the translation status of each locale file in the repository
-### ![it__IT](https://img.shields.io/badge/it__IT-96.22%25-yellow) +### ![it__IT](https://img.shields.io/badge/it__IT-96.03%25-yellow)
Missing keys in it_IT.axaml @@ -318,13 +326,15 @@ This document shows the translation status of each locale file in the repository - Text.SelfUpdate.ReleaseDate - Text.StashCM.ApplyFileChanges - Text.StashCM.Branch +- Text.SubmoduleRevisionCompare +- Text.SubmoduleRevisionCompare.OpenDetails - Text.Worktree.Branch - Text.Worktree.Head - Text.Worktree.Path
-### ![ja__JP](https://img.shields.io/badge/ja__JP-97.14%25-yellow) +### ![ja__JP](https://img.shields.io/badge/ja__JP-96.95%25-yellow)
Missing keys in ja_JP.axaml @@ -354,13 +364,15 @@ This document shows the translation status of each locale file in the repository - Text.Preferences.AI.AdditionalPrompt - Text.Preferences.General.Use24Hours - Text.StashCM.Branch +- Text.SubmoduleRevisionCompare +- Text.SubmoduleRevisionCompare.OpenDetails - Text.Worktree.Branch - Text.Worktree.Head - Text.Worktree.Path
-### ![ko__KR](https://img.shields.io/badge/ko__KR-88.88%25-yellow) +### ![ko__KR](https://img.shields.io/badge/ko__KR-88.70%25-yellow)
Missing keys in ko_KR.axaml @@ -465,6 +477,8 @@ This document shows the translation status of each locale file in the repository - Text.StashCM.ApplyFileChanges - Text.StashCM.Branch - Text.Submodule.Status.Unmerged +- Text.SubmoduleRevisionCompare +- Text.SubmoduleRevisionCompare.OpenDetails - Text.TagCM.CompareTwo - Text.TagCM.CompareWith - Text.TagCM.CompareWithHead @@ -477,7 +491,7 @@ This document shows the translation status of each locale file in the repository
-### ![pt__BR](https://img.shields.io/badge/pt__BR-67.24%25-red) +### ![pt__BR](https://img.shields.io/badge/pt__BR-67.11%25-red)
Missing keys in pt_BR.axaml @@ -768,6 +782,8 @@ This document shows the translation status of each locale file in the repository - Text.Submodule.Status.Unmerged - Text.Submodule.Update - Text.Submodule.URL +- Text.SubmoduleRevisionCompare +- Text.SubmoduleRevisionCompare.OpenDetails - Text.Tag.Tagger - Text.Tag.Time - Text.TagCM.CompareTwo @@ -806,7 +822,7 @@ This document shows the translation status of each locale file in the repository
-### ![ru__RU](https://img.shields.io/badge/ru__RU-99.29%25-yellow) +### ![ru__RU](https://img.shields.io/badge/ru__RU-99.08%25-yellow)
Missing keys in ru_RU.axaml @@ -818,10 +834,12 @@ This document shows the translation status of each locale file in the repository - Text.OpenLocalRepository.Bookmark - Text.OpenLocalRepository.Group - Text.OpenLocalRepository.Path +- Text.SubmoduleRevisionCompare +- Text.SubmoduleRevisionCompare.OpenDetails
-### ![ta__IN](https://img.shields.io/badge/ta__IN-69.29%25-red) +### ![ta__IN](https://img.shields.io/badge/ta__IN-69.14%25-red)
Missing keys in ta_IN.axaml @@ -1094,6 +1112,8 @@ This document shows the translation status of each locale file in the repository - Text.Submodule.Status.Unmerged - Text.Submodule.Update - Text.Submodule.URL +- Text.SubmoduleRevisionCompare +- Text.SubmoduleRevisionCompare.OpenDetails - Text.Tag.Tagger - Text.Tag.Time - Text.TagCM.CompareTwo @@ -1130,7 +1150,7 @@ This document shows the translation status of each locale file in the repository
-### ![uk__UA](https://img.shields.io/badge/uk__UA-70.10%25-red) +### ![uk__UA](https://img.shields.io/badge/uk__UA-69.96%25-red)
Missing keys in uk_UA.axaml @@ -1399,6 +1419,8 @@ This document shows the translation status of each locale file in the repository - Text.Submodule.Status.Unmerged - Text.Submodule.Update - Text.Submodule.URL +- Text.SubmoduleRevisionCompare +- Text.SubmoduleRevisionCompare.OpenDetails - Text.Tag.Tagger - Text.Tag.Time - Text.TagCM.CompareTwo From cff6db8dc7cd15aba68bb47b38ca2e4dbbb7feb5 Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 14 Apr 2026 20:30:19 +0800 Subject: [PATCH 19/23] enhance: disable `OPEN DETAILS` button when one of submodule revision is lost Signed-off-by: leo --- src/Models/DiffResult.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Models/DiffResult.cs b/src/Models/DiffResult.cs index 80f0678db..c85090d95 100644 --- a/src/Models/DiffResult.cs +++ b/src/Models/DiffResult.cs @@ -612,7 +612,9 @@ public class SubmoduleDiff public RevisionSubmodule Old { get; set; } = null; public RevisionSubmodule New { get; set; } = null; - public bool CanOpenDetails => File.Exists(Path.Combine(FullPath, ".git")) && Old != null && New != null; + public bool CanOpenDetails => File.Exists(Path.Combine(FullPath, ".git")) && + Old != null && Old.Commit.Author != User.Invalid && + New != null && New.Commit.Author != User.Invalid; } public class DiffResult From 5fd7551869b76656e4f425a7e6880b0256d8ab90 Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 15 Apr 2026 10:36:54 +0800 Subject: [PATCH 20/23] enhance: show the `Submodule Change Details` window on the same screen of it's parent (#2264) Signed-off-by: leo --- src/Views/DiffView.axaml.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Views/DiffView.axaml.cs b/src/Views/DiffView.axaml.cs index d01981252..43e6e8b44 100644 --- a/src/Views/DiffView.axaml.cs +++ b/src/Views/DiffView.axaml.cs @@ -45,15 +45,10 @@ private void OnGotoLastChange(object _, RoutedEventArgs e) private void OnOpenSubmoduleRevisionCompare(object sender, RoutedEventArgs e) { - var owner = TopLevel.GetTopLevel(this) as Window; - if (owner == null) - return; - if (sender is Button { DataContext: Models.SubmoduleDiff diff } && diff.CanOpenDetails) { var vm = new ViewModels.SubmoduleRevisionCompare(diff); - var dailog = new SubmoduleRevisionCompare() { DataContext = vm }; - dailog.Show(owner); + App.ShowWindow(vm); } } } From 0bf4f922477f2617905537ce207c80c136f94af4 Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 15 Apr 2026 11:00:06 +0800 Subject: [PATCH 21/23] fix: fetch AI models in background to avoid main window waiting to show (#2267) Signed-off-by: leo --- src/AI/Service.cs | 5 +---- src/App.axaml.cs | 1 + src/ViewModels/Preferences.cs | 21 ++++++++++++--------- src/Views/Launcher.axaml.cs | 4 +--- src/Views/Preferences.axaml.cs | 2 +- 5 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/AI/Service.cs b/src/AI/Service.cs index e36bc15bc..ebf9170ef 100644 --- a/src/AI/Service.cs +++ b/src/AI/Service.cs @@ -2,7 +2,6 @@ using System.ClientModel; using System.Collections.Generic; using System.Text.Json.Serialization; -using System.Threading.Tasks; using Azure.AI.OpenAI; using CommunityToolkit.Mvvm.ComponentModel; using OpenAI; @@ -55,7 +54,7 @@ public string Model set; } = string.Empty; - public async Task> FetchAvailableModelsAsync() + public void FetchAvailableModels() { var allModels = GetOpenAIClient().GetOpenAIModelClient().GetModels(); AvailableModels = new List(); @@ -71,8 +70,6 @@ public async Task> FetchAvailableModelsAsync() { Model = null; } - - return AvailableModels; } public ChatClient GetChatClient() diff --git a/src/App.axaml.cs b/src/App.axaml.cs index 44dbc977f..c2f03f7f9 100644 --- a/src/App.axaml.cs +++ b/src/App.axaml.cs @@ -547,6 +547,7 @@ private void TryLaunchAsNormal(IClassicDesktopStyleApplicationLifetime desktop) var pref = ViewModels.Preferences.Instance; pref.SetCanModify(); + pref.UpdateAvailableAIModels(); _launcher = new ViewModels.Launcher(startupRepo); desktop.MainWindow = new Views.Launcher() { DataContext = _launcher }; diff --git a/src/ViewModels/Preferences.cs b/src/ViewModels/Preferences.cs index 04ffbeb9d..c68f55599 100644 --- a/src/ViewModels/Preferences.cs +++ b/src/ViewModels/Preferences.cs @@ -629,19 +629,22 @@ public void AutoRemoveInvalidNode() RemoveInvalidRepositoriesRecursive(RepositoryNodes); } - public async Task UpdateAvailableAIModelsAsync() + public void UpdateAvailableAIModels() { - foreach (var service in OpenAIServices) + Task.Run(() => { - try + foreach (var service in OpenAIServices) { - await service.FetchAvailableModelsAsync(); - } - catch - { - // Ignore errors. + try + { + service.FetchAvailableModels(); + } + catch + { + // Ignore errors. + } } - } + }); } public void Save() diff --git a/src/Views/Launcher.axaml.cs b/src/Views/Launcher.axaml.cs index fb295c551..0c8b32c5d 100644 --- a/src/Views/Launcher.axaml.cs +++ b/src/Views/Launcher.axaml.cs @@ -106,7 +106,7 @@ public void BringToTop() Activate(); } - protected override async void OnOpened(EventArgs e) + protected override void OnOpened(EventArgs e) { base.OnOpened(e); @@ -114,8 +114,6 @@ protected override async void OnOpened(EventArgs e) var state = preferences.Layout.LauncherWindowState; if (state == WindowState.Maximized || state == WindowState.FullScreen) WindowState = WindowState.Maximized; - - await preferences.UpdateAvailableAIModelsAsync(); } protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) diff --git a/src/Views/Preferences.axaml.cs b/src/Views/Preferences.axaml.cs index 28cdf901c..3c9c2e3fb 100644 --- a/src/Views/Preferences.axaml.cs +++ b/src/Views/Preferences.axaml.cs @@ -206,7 +206,7 @@ protected override async void OnClosing(WindowClosingEventArgs e) } var preferences = ViewModels.Preferences.Instance; - await preferences.UpdateAvailableAIModelsAsync(); + preferences.UpdateAvailableAIModels(); preferences.Save(); } From 2ed3a79e7f75640ee0b2dd074829f57b6161cb02 Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 15 Apr 2026 12:14:55 +0800 Subject: [PATCH 22/23] refactor: move some code from `App` to `Views.ControlExtensions` Signed-off-by: leo --- src/App.Commands.cs | 47 ++++++++++-- src/App.axaml.cs | 80 ++------------------ src/ViewModels/BlameCommandPalette.cs | 6 +- src/ViewModels/CompareCommandPalette.cs | 6 +- src/ViewModels/Conflict.cs | 5 +- src/ViewModels/FileHistoryCommandPalette.cs | 6 +- src/ViewModels/Histories.cs | 16 ---- src/ViewModels/RepositoryCommandPalette.cs | 1 - src/Views/BlameCommandPalette.axaml.cs | 4 +- src/Views/BranchTree.axaml.cs | 10 +-- src/Views/CommitDetail.axaml.cs | 6 +- src/Views/CompareCommandPalette.axaml.cs | 4 +- src/Views/Conflict.axaml.cs | 5 +- src/Views/ControlExtensions.cs | 60 +++++++++++++++ src/Views/DiffView.axaml.cs | 2 +- src/Views/FileHistoryCommandPalette.axaml.cs | 4 +- src/Views/Histories.axaml.cs | 31 ++++++-- src/Views/Launcher.axaml.cs | 6 +- src/Views/Preferences.axaml.cs | 9 +-- src/Views/RepositoryConfigure.axaml.cs | 7 +- src/Views/RepositoryToolbar.axaml.cs | 10 +-- src/Views/RevisionFileTreeView.axaml.cs | 6 +- src/Views/SubmodulesView.axaml.cs | 2 +- src/Views/TagsView.axaml.cs | 4 +- src/Views/WorkingCopy.axaml.cs | 20 ++--- 25 files changed, 185 insertions(+), 172 deletions(-) diff --git a/src/App.Commands.cs b/src/App.Commands.cs index 541750705..56c150098 100644 --- a/src/App.Commands.cs +++ b/src/App.Commands.cs @@ -37,12 +37,47 @@ public static bool IsCheckForUpdateCommandVisible } } - public static readonly Command OpenPreferencesCommand = new Command(async _ => await ShowDialog(new Views.Preferences())); - public static readonly Command OpenHotkeysCommand = new Command(async _ => await ShowDialog(new Views.Hotkeys())); - public static readonly Command OpenAppDataDirCommand = new Command(_ => Native.OS.OpenInFileManager(Native.OS.DataDir)); - public static readonly Command OpenAboutCommand = new Command(async _ => await ShowDialog(new Views.About())); - public static readonly Command CheckForUpdateCommand = new Command(_ => (Current as App)?.Check4Update(true)); - public static readonly Command QuitCommand = new Command(_ => Quit(0)); + public static readonly Command OpenPreferencesCommand = new Command(async _ => + { + if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner }) + { + var dialog = new Views.Preferences(); + await dialog.ShowDialog(owner); + } + }); + + public static readonly Command OpenHotkeysCommand = new Command(async _ => + { + if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner }) + { + var dialog = new Views.Hotkeys(); + await dialog.ShowDialog(owner); + } + }); + + public static readonly Command OpenAppDataDirCommand = new Command(_ => + { + Native.OS.OpenInFileManager(Native.OS.DataDir); + }); + + public static readonly Command OpenAboutCommand = new Command(async _ => + { + if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner }) + { + var dialog = new Views.About(); + await dialog.ShowDialog(owner); + } + }); + + public static readonly Command CheckForUpdateCommand = new Command(_ => + { + (Current as App)?.Check4Update(true); + }); + + public static readonly Command QuitCommand = new Command(_ => + { + Quit(0); + }); public static readonly Command HideAppCommand = new Command(_ => { diff --git a/src/App.axaml.cs b/src/App.axaml.cs index c2f03f7f9..8d6bafb71 100644 --- a/src/App.axaml.cs +++ b/src/App.axaml.cs @@ -75,79 +75,6 @@ public static AppBuilder BuildAvaloniaApp() #endregion #region Utility Functions - public static Task ShowDialog(object data, Window owner = null) - { - if (owner == null) - { - if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } mainWindow }) - owner = mainWindow; - else - return null; - } - - if (data is Views.ChromelessWindow window) - return window.ShowDialog(owner); - - window = Views.ControlExtensions.CreateFromViewModels(data) as Views.ChromelessWindow; - if (window != null) - { - window.DataContext = data; - return window.ShowDialog(owner); - } - - return null; - } - - public static void ShowWindow(object data) - { - if (data is not Views.ChromelessWindow window) - { - window = Views.ControlExtensions.CreateFromViewModels(data) as Views.ChromelessWindow; - if (window == null) - return; - - window.DataContext = data; - } - - do - { - if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { Windows: { Count: > 0 } windows }) - { - // Try to find the actived window (fall back to `MainWindow`) - Window actived = windows[0]; - if (!actived.IsActive) - { - for (var i = 1; i < windows.Count; i++) - { - var test = windows[i]; - if (test.IsActive) - { - actived = test; - break; - } - } - } - - // Get the screen where current window locates. - var screen = actived.Screens.ScreenFromWindow(actived) ?? actived.Screens.Primary; - if (screen == null) - break; - - // Calculate the startup position (Center Screen Mode) of target window - var rect = new PixelRect(PixelSize.FromSize(window.ClientSize, actived.DesktopScaling)); - var centeredRect = screen.WorkingArea.CenterRect(rect); - if (actived.Screens.ScreenFromPoint(centeredRect.Position) == null) - break; - - // Use the startup position - window.WindowStartupLocation = WindowStartupLocation.Manual; - window.Position = centeredRect.Position; - } - } while (false); - - window.Show(); - } - public static async Task AskConfirmAsync(string message, Models.ConfirmButtonType buttonType = Models.ConfirmButtonType.OkCancel) { if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner }) @@ -620,7 +547,12 @@ private void ShowSelfUpdateResult(object data) { Dispatcher.UIThread.Invoke(async () => { - await ShowDialog(new ViewModels.SelfUpdate { Data = data }); + if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner }) + { + var ctx = new ViewModels.SelfUpdate { Data = data }; + var dialog = new Views.SelfUpdate() { DataContext = ctx }; + await dialog.ShowDialog(owner); + } }); } catch diff --git a/src/ViewModels/BlameCommandPalette.cs b/src/ViewModels/BlameCommandPalette.cs index c277a47c1..e73430872 100644 --- a/src/ViewModels/BlameCommandPalette.cs +++ b/src/ViewModels/BlameCommandPalette.cs @@ -65,14 +65,12 @@ public void ClearFilter() Filter = string.Empty; } - public void Launch() + public Blame Launch() { _repoFiles.Clear(); _visibleFiles.Clear(); Close(); - - if (!string.IsNullOrEmpty(_selectedFile)) - App.ShowWindow(new Blame(_repo, _selectedFile, _head)); + return !string.IsNullOrEmpty(_selectedFile) ? new Blame(_repo, _selectedFile, _head) : null; } private void UpdateVisible() diff --git a/src/ViewModels/CompareCommandPalette.cs b/src/ViewModels/CompareCommandPalette.cs index bebffe376..7eb49efd2 100644 --- a/src/ViewModels/CompareCommandPalette.cs +++ b/src/ViewModels/CompareCommandPalette.cs @@ -44,13 +44,11 @@ public void ClearFilter() Filter = string.Empty; } - public void Launch() + public Compare Launch() { _refs.Clear(); Close(); - - if (_compareTo != null) - App.ShowWindow(new Compare(_repo, _basedOn, _compareTo)); + return _compareTo != null ? new Compare(_repo, _basedOn, _compareTo) : null; } private void UpdateRefs() diff --git a/src/ViewModels/Conflict.cs b/src/ViewModels/Conflict.cs index 06ac60773..380ad68dc 100644 --- a/src/ViewModels/Conflict.cs +++ b/src/ViewModels/Conflict.cs @@ -73,10 +73,9 @@ public async Task UseMineAsync() await _wc.UseMineAsync([_change]); } - public async Task MergeAsync() + public MergeConflictEditor CreateOpenMergeEditorRequest() { - if (CanMerge) - await App.ShowDialog(new MergeConflictEditor(_repo, _head, _change.Path)); + return CanMerge ? new MergeConflictEditor(_repo, _head, _change.Path) : null; } public async Task MergeExternalAsync() diff --git a/src/ViewModels/FileHistoryCommandPalette.cs b/src/ViewModels/FileHistoryCommandPalette.cs index 8a371febf..097e85669 100644 --- a/src/ViewModels/FileHistoryCommandPalette.cs +++ b/src/ViewModels/FileHistoryCommandPalette.cs @@ -60,14 +60,12 @@ public void ClearFilter() Filter = string.Empty; } - public void Launch() + public FileHistories Launch() { _repoFiles.Clear(); _visibleFiles.Clear(); Close(); - - if (!string.IsNullOrEmpty(_selectedFile)) - App.ShowWindow(new FileHistories(_repo, _selectedFile)); + return !string.IsNullOrEmpty(_selectedFile) ? new FileHistories(_repo, _selectedFile) : null; } private void UpdateVisible() diff --git a/src/ViewModels/Histories.cs b/src/ViewModels/Histories.cs index d7a81b0f4..4d0fd02f9 100644 --- a/src/ViewModels/Histories.cs +++ b/src/ViewModels/Histories.cs @@ -418,22 +418,6 @@ public async Task DropHeadAsync(Models.Commit head) _repo.ShowPopup(new DropHead(_repo, head, parent)); } - public async Task InteractiveRebaseAsync(Models.Commit commit, Models.InteractiveRebaseAction act) - { - var prefill = new InteractiveRebasePrefill(commit.SHA, act); - var start = act switch - { - Models.InteractiveRebaseAction.Squash or Models.InteractiveRebaseAction.Fixup => $"{commit.SHA}~~", - _ => $"{commit.SHA}~", - }; - - var on = await new Commands.QuerySingleCommit(_repo.FullPath, start).GetResultAsync(); - if (on == null) - _repo.SendNotification($"Can not squash current commit into parent!", true); - else - await App.ShowDialog(new InteractiveRebase(_repo, on, prefill)); - } - public async Task GetCommitFullMessageAsync(Models.Commit commit) { return await new Commands.QueryCommitFullMessage(_repo.FullPath, commit.SHA) diff --git a/src/ViewModels/RepositoryCommandPalette.cs b/src/ViewModels/RepositoryCommandPalette.cs index 6d6c77c84..2b57414a7 100644 --- a/src/ViewModels/RepositoryCommandPalette.cs +++ b/src/ViewModels/RepositoryCommandPalette.cs @@ -73,7 +73,6 @@ public RepositoryCommandPalette(Repository repo) _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; diff --git a/src/Views/BlameCommandPalette.axaml.cs b/src/Views/BlameCommandPalette.axaml.cs index 32c5381fa..56de2d6db 100644 --- a/src/Views/BlameCommandPalette.axaml.cs +++ b/src/Views/BlameCommandPalette.axaml.cs @@ -19,7 +19,7 @@ protected override void OnKeyDown(KeyEventArgs e) if (e.Key == Key.Enter) { - vm.Launch(); + this.ShowWindow(vm.Launch()); e.Handled = true; } else if (e.Key == Key.Up) @@ -55,7 +55,7 @@ private void OnItemTapped(object sender, TappedEventArgs e) { if (DataContext is ViewModels.BlameCommandPalette vm) { - vm.Launch(); + this.ShowWindow(vm.Launch()); e.Handled = true; } } diff --git a/src/Views/BranchTree.axaml.cs b/src/Views/BranchTree.axaml.cs index c7ca52767..603666b38 100644 --- a/src/Views/BranchTree.axaml.cs +++ b/src/Views/BranchTree.axaml.cs @@ -537,7 +537,7 @@ private void OnTreeContextRequested(object _1, ContextRequestedEventArgs _2) compare.Icon = this.CreateMenuIcon("Icons.Compare"); compare.Click += (_, ev) => { - App.ShowWindow(new ViewModels.Compare(repo, branches[0], branches[1])); + this.ShowWindow(new ViewModels.Compare(repo, branches[0], branches[1])); ev.Handled = true; }; menu.Items.Add(compare); @@ -819,7 +819,7 @@ private ContextMenu CreateContextMenuForLocalBranch(ViewModels.Repository repo, interactiveRebase.Click += async (_, e) => { var commit = await new Commands.QuerySingleCommit(repo.FullPath, branch.Head).GetResultAsync(); - await App.ShowDialog(new ViewModels.InteractiveRebase(repo, commit)); + await this.ShowDialogAsync(new ViewModels.InteractiveRebase(repo, commit)); e.Handled = true; }; @@ -853,7 +853,7 @@ private ContextMenu CreateContextMenuForLocalBranch(ViewModels.Repository repo, compareWithCurrent.Icon = this.CreateMenuIcon("Icons.Compare"); compareWithCurrent.Click += (_, _) => { - App.ShowWindow(new ViewModels.Compare(repo, branch, current)); + this.ShowWindow(new ViewModels.Compare(repo, branch, current)); }; var compareWith = new MenuItem(); @@ -1154,7 +1154,7 @@ public ContextMenu CreateContextMenuForRemoteBranch(ViewModels.Repository repo, interactiveRebase.Click += async (_, e) => { var commit = await new Commands.QuerySingleCommit(repo.FullPath, branch.Head).GetResultAsync(); - await App.ShowDialog(new ViewModels.InteractiveRebase(repo, commit)); + await this.ShowDialogAsync(new ViewModels.InteractiveRebase(repo, commit)); e.Handled = true; }; @@ -1163,7 +1163,7 @@ public ContextMenu CreateContextMenuForRemoteBranch(ViewModels.Repository repo, compareWithHead.Icon = this.CreateMenuIcon("Icons.Compare"); compareWithHead.Click += (_, _) => { - App.ShowWindow(new ViewModels.Compare(repo, branch, current)); + this.ShowWindow(new ViewModels.Compare(repo, branch, current)); }; var compareWith = new MenuItem(); diff --git a/src/Views/CommitDetail.axaml.cs b/src/Views/CommitDetail.axaml.cs index f0e55665c..99969853c 100644 --- a/src/Views/CommitDetail.axaml.cs +++ b/src/Views/CommitDetail.axaml.cs @@ -38,7 +38,7 @@ public ContextMenu CreateChangeContextMenuByFolder(ViewModels.ChangeTreeNode nod history.Icon = this.CreateMenuIcon("Icons.Histories"); history.Click += (_, ev) => { - App.ShowWindow(new ViewModels.DirHistories(repo, node.FullPath, commit.SHA)); + this.ShowWindow(new ViewModels.DirHistories(repo, node.FullPath, commit.SHA)); ev.Handled = true; }; @@ -273,7 +273,7 @@ public ContextMenu CreateChangeContextMenu(Models.Change change) history.Icon = this.CreateMenuIcon("Icons.Histories"); history.Click += (_, ev) => { - App.ShowWindow(new ViewModels.FileHistories(repo.FullPath, change.Path, commit.SHA)); + this.ShowWindow(new ViewModels.FileHistories(repo.FullPath, change.Path, commit.SHA)); ev.Handled = true; }; @@ -283,7 +283,7 @@ public ContextMenu CreateChangeContextMenu(Models.Change change) blame.IsEnabled = change.Index != Models.ChangeState.Deleted; blame.Click += (_, ev) => { - App.ShowWindow(new ViewModels.Blame(repo.FullPath, change.Path, commit)); + this.ShowWindow(new ViewModels.Blame(repo.FullPath, change.Path, commit)); ev.Handled = true; }; diff --git a/src/Views/CompareCommandPalette.axaml.cs b/src/Views/CompareCommandPalette.axaml.cs index e0c0c8255..44809f278 100644 --- a/src/Views/CompareCommandPalette.axaml.cs +++ b/src/Views/CompareCommandPalette.axaml.cs @@ -19,7 +19,7 @@ protected override void OnKeyDown(KeyEventArgs e) if (e.Key == Key.Enter) { - vm.Launch(); + this.ShowWindow(vm.Launch()); e.Handled = true; } else if (e.Key == Key.Up) @@ -55,7 +55,7 @@ private void OnItemTapped(object sender, TappedEventArgs e) { if (DataContext is ViewModels.CompareCommandPalette vm) { - vm.Launch(); + this.ShowWindow(vm.Launch()); e.Handled = true; } } diff --git a/src/Views/Conflict.axaml.cs b/src/Views/Conflict.axaml.cs index cdd616f9c..0de67ccc9 100644 --- a/src/Views/Conflict.axaml.cs +++ b/src/Views/Conflict.axaml.cs @@ -40,7 +40,10 @@ private async void OnUseMine(object _, RoutedEventArgs e) private async void OnMerge(object _, RoutedEventArgs e) { if (DataContext is ViewModels.Conflict vm) - await vm.MergeAsync(); + { + var request = vm.CreateOpenMergeEditorRequest(); + await this.ShowDialogAsync(request); + } e.Handled = true; } diff --git a/src/Views/ControlExtensions.cs b/src/Views/ControlExtensions.cs index 5f043d8a0..33c20b4f2 100644 --- a/src/Views/ControlExtensions.cs +++ b/src/Views/ControlExtensions.cs @@ -1,5 +1,7 @@ using System; using System.Threading.Tasks; + +using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Shapes; using Avalonia.Media; @@ -44,5 +46,63 @@ public static Path CreateMenuIcon(this Control control, string iconKey) return null; } + + public static void ShowWindow(this Control control, object data) + { + if (data == null) + return; + + if (data is not ChromelessWindow window) + { + window = CreateFromViewModels(data) as ChromelessWindow; + if (window == null) + return; + + window.DataContext = data; + } + + do + { + var owner = TopLevel.GetTopLevel(control) as Window; + if (owner != null) + { + // Get the screen where current window locates. + var screen = owner.Screens.ScreenFromWindow(owner) ?? owner.Screens.Primary; + if (screen == null) + break; + + // Calculate the startup position (Center Screen Mode) of target window + var rect = new PixelRect(PixelSize.FromSize(window.ClientSize, owner.DesktopScaling)); + var centeredRect = screen.WorkingArea.CenterRect(rect); + if (owner.Screens.ScreenFromPoint(centeredRect.Position) == null) + break; + + // Use the startup position + window.WindowStartupLocation = WindowStartupLocation.Manual; + window.Position = centeredRect.Position; + } + } while (false); + + window.Show(); + } + + public static Task ShowDialogAsync(this Control control, object data) + { + var owner = TopLevel.GetTopLevel(control) as Window; + if (owner == null) + return null; + + if (data is ChromelessWindow window) + return window.ShowDialog(owner); + + window = CreateFromViewModels(data) as ChromelessWindow; + if (window != null) + { + window.DataContext = data; + return window.ShowDialog(owner); + } + + return null; + } } } diff --git a/src/Views/DiffView.axaml.cs b/src/Views/DiffView.axaml.cs index 43e6e8b44..504df14d3 100644 --- a/src/Views/DiffView.axaml.cs +++ b/src/Views/DiffView.axaml.cs @@ -48,7 +48,7 @@ private void OnOpenSubmoduleRevisionCompare(object sender, RoutedEventArgs e) if (sender is Button { DataContext: Models.SubmoduleDiff diff } && diff.CanOpenDetails) { var vm = new ViewModels.SubmoduleRevisionCompare(diff); - App.ShowWindow(vm); + this.ShowWindow(vm); } } } diff --git a/src/Views/FileHistoryCommandPalette.axaml.cs b/src/Views/FileHistoryCommandPalette.axaml.cs index 2361e9712..4038c44b2 100644 --- a/src/Views/FileHistoryCommandPalette.axaml.cs +++ b/src/Views/FileHistoryCommandPalette.axaml.cs @@ -19,7 +19,7 @@ protected override void OnKeyDown(KeyEventArgs e) if (e.Key == Key.Enter) { - vm.Launch(); + this.ShowWindow(vm.Launch()); e.Handled = true; } else if (e.Key == Key.Up) @@ -55,7 +55,7 @@ private void OnItemTapped(object sender, TappedEventArgs e) { if (DataContext is ViewModels.FileHistoryCommandPalette vm) { - vm.Launch(); + this.ShowWindow(vm.Launch()); e.Handled = true; } } diff --git a/src/Views/Histories.axaml.cs b/src/Views/Histories.axaml.cs index 39312ec6e..f042a78b4 100644 --- a/src/Views/Histories.axaml.cs +++ b/src/Views/Histories.axaml.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text; +using System.Threading.Tasks; using Avalonia; using Avalonia.Collections; @@ -832,7 +833,7 @@ private ContextMenu CreateContextMenuForSingleCommit(ViewModels.Repository repo, manually.Icon = this.CreateMenuIcon("Icons.InteractiveRebase"); manually.Click += async (_, e) => { - await App.ShowDialog(new ViewModels.InteractiveRebase(repo, commit)); + await this.ShowDialogAsync(new ViewModels.InteractiveRebase(repo, commit)); e.Handled = true; }; @@ -841,7 +842,7 @@ private ContextMenu CreateContextMenuForSingleCommit(ViewModels.Repository repo, reword.Icon = this.CreateMenuIcon("Icons.Rename"); reword.Click += async (_, e) => { - await vm.InteractiveRebaseAsync(commit, Models.InteractiveRebaseAction.Reword); + await InteractiveRebaseWithPrefillActionAsync(repo, commit, Models.InteractiveRebaseAction.Reword); e.Handled = true; }; @@ -850,7 +851,7 @@ private ContextMenu CreateContextMenuForSingleCommit(ViewModels.Repository repo, edit.Icon = this.CreateMenuIcon("Icons.Edit"); edit.Click += async (_, e) => { - await vm.InteractiveRebaseAsync(commit, Models.InteractiveRebaseAction.Edit); + await InteractiveRebaseWithPrefillActionAsync(repo, commit, Models.InteractiveRebaseAction.Edit); e.Handled = true; }; @@ -859,7 +860,7 @@ private ContextMenu CreateContextMenuForSingleCommit(ViewModels.Repository repo, squash.Icon = this.CreateMenuIcon("Icons.SquashIntoParent"); squash.Click += async (_, e) => { - await vm.InteractiveRebaseAsync(commit, Models.InteractiveRebaseAction.Squash); + await InteractiveRebaseWithPrefillActionAsync(repo, commit, Models.InteractiveRebaseAction.Squash); e.Handled = true; }; @@ -868,7 +869,7 @@ private ContextMenu CreateContextMenuForSingleCommit(ViewModels.Repository repo, fixup.Icon = this.CreateMenuIcon("Icons.Fix"); fixup.Click += async (_, e) => { - await vm.InteractiveRebaseAsync(commit, Models.InteractiveRebaseAction.Fixup); + await InteractiveRebaseWithPrefillActionAsync(repo, commit, Models.InteractiveRebaseAction.Fixup); e.Handled = true; }; @@ -877,7 +878,7 @@ private ContextMenu CreateContextMenuForSingleCommit(ViewModels.Repository repo, drop.Icon = this.CreateMenuIcon("Icons.Clear"); drop.Click += async (_, e) => { - await vm.InteractiveRebaseAsync(commit, Models.InteractiveRebaseAction.Drop); + await InteractiveRebaseWithPrefillActionAsync(repo, commit, Models.InteractiveRebaseAction.Drop); e.Handled = true; }; @@ -902,7 +903,7 @@ private ContextMenu CreateContextMenuForSingleCommit(ViewModels.Repository repo, interactiveRebase.Icon = this.CreateMenuIcon("Icons.InteractiveRebase"); interactiveRebase.Click += async (_, e) => { - await App.ShowDialog(new ViewModels.InteractiveRebase(repo, commit)); + await this.ShowDialogAsync(new ViewModels.InteractiveRebase(repo, commit)); e.Handled = true; }; @@ -1417,6 +1418,22 @@ private void FillTagMenu(ContextMenu menu, ViewModels.Repository repo, Models.Ta menu.Items.Add(submenu); } + private async Task InteractiveRebaseWithPrefillActionAsync(ViewModels.Repository repo, Models.Commit target, Models.InteractiveRebaseAction action) + { + var prefill = new ViewModels.InteractiveRebasePrefill(target.SHA, action); + var start = action switch + { + Models.InteractiveRebaseAction.Squash or Models.InteractiveRebaseAction.Fixup => $"{target.SHA}~~", + _ => $"{target.SHA}~", + }; + + var on = await new Commands.QuerySingleCommit(repo.FullPath, start).GetResultAsync(); + if (on == null) + repo.SendNotification($"Commit '{start}' is not a valid revision for `git rebase -i`!", true); + else + await this.ShowDialogAsync(new ViewModels.InteractiveRebase(repo, on, prefill)); + } + private double _lastGraphStartY = 0; private double _lastGraphClipWidth = 0; private double _lastGraphRowHeight = 0; diff --git a/src/Views/Launcher.axaml.cs b/src/Views/Launcher.axaml.cs index 0c8b32c5d..20d5617aa 100644 --- a/src/Views/Launcher.axaml.cs +++ b/src/Views/Launcher.axaml.cs @@ -172,14 +172,14 @@ protected override async void OnKeyDown(KeyEventArgs e) { if (e is { KeyModifiers: KeyModifiers.Control, Key: Key.OemComma }) { - await App.ShowDialog(new Preferences()); + await this.ShowDialogAsync(new Preferences()); e.Handled = true; return; } if (e is { KeyModifiers: KeyModifiers.None, Key: Key.F1 }) { - await App.ShowDialog(new Hotkeys()); + await this.ShowDialogAsync(new Hotkeys()); e.Handled = true; return; } @@ -404,7 +404,7 @@ private void OnOpenWorkspaceMenu(object sender, RoutedEventArgs e) configure.Header = App.Text("Workspace.Configure"); configure.Click += async (_, ev) => { - await App.ShowDialog(new ViewModels.ConfigureWorkspace()); + await this.ShowDialogAsync(new ViewModels.ConfigureWorkspace()); ev.Handled = true; }; menu.Items.Add(configure); diff --git a/src/Views/Preferences.axaml.cs b/src/Views/Preferences.axaml.cs index 3c9c2e3fb..6ab603f02 100644 --- a/src/Views/Preferences.axaml.cs +++ b/src/Views/Preferences.axaml.cs @@ -386,7 +386,7 @@ private async void OnUseNativeWindowFrameChanged(object sender, RoutedEventArgs if (sender is CheckBox box) { ViewModels.Preferences.Instance.UseSystemWindowFrame = box.IsChecked == true; - await App.ShowDialog(new ConfirmRestart()); + await this.ShowDialogAsync(new ConfirmRestart()); } e.Handled = true; @@ -486,12 +486,7 @@ private async void EditCustomActionControls(object sender, RoutedEventArgs e) if (sender is not Button { DataContext: Models.CustomAction act }) return; - var dialog = new ConfigureCustomActionControls() - { - DataContext = new ViewModels.ConfigureCustomActionControls(act.Controls) - }; - - await dialog.ShowDialog(this); + await this.ShowDialogAsync(new ViewModels.ConfigureCustomActionControls(act.Controls)); e.Handled = true; } diff --git a/src/Views/RepositoryConfigure.axaml.cs b/src/Views/RepositoryConfigure.axaml.cs index 2c5622df1..bc445d2a7 100644 --- a/src/Views/RepositoryConfigure.axaml.cs +++ b/src/Views/RepositoryConfigure.axaml.cs @@ -56,12 +56,7 @@ private async void EditCustomActionControls(object sender, RoutedEventArgs e) if (sender is not Button { DataContext: Models.CustomAction act }) return; - var dialog = new ConfigureCustomActionControls() - { - DataContext = new ViewModels.ConfigureCustomActionControls(act.Controls) - }; - - await dialog.ShowDialog(this); + await this.ShowDialogAsync(new ViewModels.ConfigureCustomActionControls(act.Controls)); e.Handled = true; } diff --git a/src/Views/RepositoryToolbar.axaml.cs b/src/Views/RepositoryToolbar.axaml.cs index e224b1ba0..72bb04623 100644 --- a/src/Views/RepositoryToolbar.axaml.cs +++ b/src/Views/RepositoryToolbar.axaml.cs @@ -138,7 +138,7 @@ private async void OpenStatistics(object _, RoutedEventArgs e) { if (DataContext is ViewModels.Repository repo) { - await App.ShowDialog(new ViewModels.Statistics(repo.FullPath)); + await this.ShowDialogAsync(new ViewModels.Statistics(repo.FullPath)); e.Handled = true; } } @@ -147,7 +147,7 @@ private async void OpenConfigure(object sender, RoutedEventArgs e) { if (DataContext is ViewModels.Repository repo) { - await App.ShowDialog(new ViewModels.RepositoryConfigure(repo)); + await this.ShowDialogAsync(new ViewModels.RepositoryConfigure(repo)); e.Handled = true; } } @@ -387,7 +387,7 @@ private void OpenGitLFSMenu(object sender, RoutedEventArgs ev) { locks.Click += async (_, e) => { - await App.ShowDialog(new ViewModels.LFSLocks(repo, repo.Remotes[0].Name)); + await this.ShowDialogAsync(new ViewModels.LFSLocks(repo, repo.Remotes[0].Name)); e.Handled = true; }; } @@ -400,7 +400,7 @@ private void OpenGitLFSMenu(object sender, RoutedEventArgs ev) lockRemote.Header = remoteName; lockRemote.Click += async (_, e) => { - await App.ShowDialog(new ViewModels.LFSLocks(repo, remoteName)); + await this.ShowDialogAsync(new ViewModels.LFSLocks(repo, remoteName)); e.Handled = true; }; locks.Items.Add(lockRemote); @@ -494,7 +494,7 @@ private async void OpenGitLogs(object sender, RoutedEventArgs e) { if (DataContext is ViewModels.Repository repo) { - await App.ShowDialog(new ViewModels.ViewLogs(repo)); + await this.ShowDialogAsync(new ViewModels.ViewLogs(repo)); e.Handled = true; } } diff --git a/src/Views/RevisionFileTreeView.axaml.cs b/src/Views/RevisionFileTreeView.axaml.cs index 4ac1901ce..e9a0e22fe 100644 --- a/src/Views/RevisionFileTreeView.axaml.cs +++ b/src/Views/RevisionFileTreeView.axaml.cs @@ -472,7 +472,7 @@ private ContextMenu CreateRevisionFileContextMenuByFolder(ViewModels.Repository history.Icon = this.CreateMenuIcon("Icons.Histories"); history.Click += (_, ev) => { - App.ShowWindow(new ViewModels.DirHistories(repo, path, commit.SHA)); + this.ShowWindow(new ViewModels.DirHistories(repo, path, commit.SHA)); ev.Handled = true; }; @@ -601,7 +601,7 @@ private ContextMenu CreateRevisionFileContextMenu(ViewModels.Repository repo, Vi history.Icon = this.CreateMenuIcon("Icons.Histories"); history.Click += (_, ev) => { - App.ShowWindow(new ViewModels.FileHistories(repo.FullPath, file.Path, commit.SHA)); + this.ShowWindow(new ViewModels.FileHistories(repo.FullPath, file.Path, commit.SHA)); ev.Handled = true; }; @@ -611,7 +611,7 @@ private ContextMenu CreateRevisionFileContextMenu(ViewModels.Repository repo, Vi blame.IsEnabled = file.Type == Models.ObjectType.Blob; blame.Click += (_, ev) => { - App.ShowWindow(new ViewModels.Blame(repo.FullPath, file.Path, commit)); + this.ShowWindow(new ViewModels.Blame(repo.FullPath, file.Path, commit)); ev.Handled = true; }; diff --git a/src/Views/SubmodulesView.axaml.cs b/src/Views/SubmodulesView.axaml.cs index e45be0cac..c82050498 100644 --- a/src/Views/SubmodulesView.axaml.cs +++ b/src/Views/SubmodulesView.axaml.cs @@ -257,7 +257,7 @@ private void OnItemContextRequested(object sender, ContextRequestedEventArgs e) histories.Icon = this.CreateMenuIcon("Icons.Histories"); histories.Click += (_, ev) => { - App.ShowWindow(new ViewModels.FileHistories(repo.FullPath, submodule.Path)); + this.ShowWindow(new ViewModels.FileHistories(repo.FullPath, submodule.Path)); ev.Handled = true; }; diff --git a/src/Views/TagsView.axaml.cs b/src/Views/TagsView.axaml.cs index 0e66a5cc5..3878fcab2 100644 --- a/src/Views/TagsView.axaml.cs +++ b/src/Views/TagsView.axaml.cs @@ -271,7 +271,7 @@ private void OnTagsContextMenuRequested(object sender, ContextRequestedEventArgs compareWithHead.Icon = this.CreateMenuIcon("Icons.Compare"); compareWithHead.Click += (_, _) => { - App.ShowWindow(new ViewModels.Compare(repo, tag, repo.CurrentBranch)); + this.ShowWindow(new ViewModels.Compare(repo, tag, repo.CurrentBranch)); }; var compareWith = new MenuItem(); @@ -387,7 +387,7 @@ private void OnTagsContextMenuRequested(object sender, ContextRequestedEventArgs if (based.CreatorDate > to.CreatorDate) (based, to) = (to, based); - App.ShowWindow(new ViewModels.Compare(repo, based, to)); + this.ShowWindow(new ViewModels.Compare(repo, based, to)); ev.Handled = true; }; menu.Items.Add(compare); diff --git a/src/Views/WorkingCopy.axaml.cs b/src/Views/WorkingCopy.axaml.cs index ea7a215d2..9a5969def 100644 --- a/src/Views/WorkingCopy.axaml.cs +++ b/src/Views/WorkingCopy.axaml.cs @@ -34,7 +34,7 @@ private async void OnOpenAssumeUnchanged(object sender, RoutedEventArgs e) { var repoView = this.FindAncestorOfType(); if (repoView is { DataContext: ViewModels.Repository repo }) - await App.ShowDialog(new ViewModels.AssumeUnchangedManager(repo)); + await this.ShowDialogAsync(new ViewModels.AssumeUnchangedManager(repo)); e.Handled = true; } @@ -360,7 +360,7 @@ private ContextMenu CreateContextMenuForUnstagedChanges(ViewModels.WorkingCopy v mergeBuiltin.Click += async (_, e) => { var head = await new Commands.QuerySingleCommit(repo.FullPath, "HEAD").GetResultAsync(); - await App.ShowDialog(new ViewModels.MergeConflictEditor(repo, head, change.Path)); + await this.ShowDialogAsync(new ViewModels.MergeConflictEditor(repo, head, change.Path)); e.Handled = true; }; @@ -648,7 +648,7 @@ private ContextMenu CreateContextMenuForUnstagedChanges(ViewModels.WorkingCopy v history.Icon = this.CreateMenuIcon("Icons.Histories"); history.Click += (_, e) => { - App.ShowWindow(new ViewModels.DirHistories(repo, selectedSingleFolder)); + this.ShowWindow(new ViewModels.DirHistories(repo, selectedSingleFolder)); e.Handled = true; }; @@ -662,7 +662,7 @@ private ContextMenu CreateContextMenuForUnstagedChanges(ViewModels.WorkingCopy v history.Icon = this.CreateMenuIcon("Icons.Histories"); history.Click += (_, e) => { - App.ShowWindow(new ViewModels.FileHistories(repo.FullPath, change.Path)); + this.ShowWindow(new ViewModels.FileHistories(repo.FullPath, change.Path)); e.Handled = true; }; @@ -672,7 +672,7 @@ private ContextMenu CreateContextMenuForUnstagedChanges(ViewModels.WorkingCopy v blame.Click += async (_, ev) => { var commit = await new Commands.QuerySingleCommit(repo.FullPath, "HEAD").GetResultAsync(); - App.ShowWindow(new ViewModels.Blame(repo.FullPath, change.Path, commit)); + this.ShowWindow(new ViewModels.Blame(repo.FullPath, change.Path, commit)); ev.Handled = true; }; @@ -872,7 +872,7 @@ private ContextMenu CreateContextMenuForUnstagedChanges(ViewModels.WorkingCopy v history.Icon = this.CreateMenuIcon("Icons.Histories"); history.Click += (_, e) => { - App.ShowWindow(new ViewModels.DirHistories(repo, selectedSingleFolder)); + this.ShowWindow(new ViewModels.DirHistories(repo, selectedSingleFolder)); e.Handled = true; }; @@ -1119,7 +1119,7 @@ public ContextMenu CreateContextMenuForStagedChanges(ViewModels.WorkingCopy vm, history.Icon = this.CreateMenuIcon("Icons.Histories"); history.Click += (_, e) => { - App.ShowWindow(new ViewModels.DirHistories(repo, selectedSingleFolder)); + this.ShowWindow(new ViewModels.DirHistories(repo, selectedSingleFolder)); e.Handled = true; }; @@ -1133,7 +1133,7 @@ public ContextMenu CreateContextMenuForStagedChanges(ViewModels.WorkingCopy vm, history.Icon = this.CreateMenuIcon("Icons.Histories"); history.Click += (_, e) => { - App.ShowWindow(new ViewModels.FileHistories(repo.FullPath, change.Path)); + this.ShowWindow(new ViewModels.FileHistories(repo.FullPath, change.Path)); e.Handled = true; }; @@ -1143,7 +1143,7 @@ public ContextMenu CreateContextMenuForStagedChanges(ViewModels.WorkingCopy vm, blame.Click += async (_, e) => { var commit = await new Commands.QuerySingleCommit(repo.FullPath, "HEAD").GetResultAsync(); - App.ShowWindow(new ViewModels.Blame(repo.FullPath, change.Path, commit)); + this.ShowWindow(new ViewModels.Blame(repo.FullPath, change.Path, commit)); e.Handled = true; }; @@ -1263,7 +1263,7 @@ public ContextMenu CreateContextMenuForStagedChanges(ViewModels.WorkingCopy vm, history.Icon = this.CreateMenuIcon("Icons.Histories"); history.Click += (_, e) => { - App.ShowWindow(new ViewModels.DirHistories(repo, selectedSingleFolder)); + this.ShowWindow(new ViewModels.DirHistories(repo, selectedSingleFolder)); e.Handled = true; }; From 161d7b75aef89702b6345a4ffe4f8ce8130c6bc8 Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 8 Apr 2026 11:52:00 +0800 Subject: [PATCH 23/23] project: upgrade `AvaloniaUI` to `12.0.0` Signed-off-by: leo --- depends/AvaloniaEdit | 2 +- src/App.axaml.cs | 5 - src/Models/ThemeOverrides.cs | 1 - src/Native/Linux.cs | 5 +- src/Native/MacOS.cs | 3 +- src/Native/OS.cs | 7 -- src/Native/Windows.cs | 39 +------ src/Resources/Styles.axaml | 81 ++++++-------- src/SourceGit.csproj | 14 +-- src/Views/AIAssistant.axaml.cs | 1 + src/Views/AddRemote.axaml | 6 +- src/Views/AddSubmodule.axaml | 4 +- src/Views/AddWorktree.axaml | 4 +- src/Views/Apply.axaml | 2 +- src/Views/Archive.axaml | 2 +- src/Views/Avatar.cs | 10 +- src/Views/BranchTree.axaml.cs | 2 +- src/Views/CaptionButtons.axaml | 6 +- src/Views/CaptionButtons.axaml.cs | 6 +- src/Views/ChangeSubmoduleUrl.axaml | 2 +- src/Views/ChromelessWindow.cs | 19 ---- src/Views/Clone.axaml | 6 +- src/Views/CommitChanges.axaml | 2 +- src/Views/CommitMessageToolBox.axaml.cs | 1 + src/Views/CommitSubjectPresenter.cs | 9 +- src/Views/Compare.axaml | 2 +- src/Views/ControlExtensions.cs | 1 + .../ConventionalCommitMessageBuilder.axaml | 8 +- src/Views/CreateBranch.axaml | 2 +- src/Views/CreateTag.axaml | 4 +- src/Views/DateTimePresenter.cs | 18 +-- src/Views/EditRemote.axaml | 6 +- src/Views/ExecuteCustomAction.axaml | 4 +- src/Views/GitFlowStart.axaml | 2 +- src/Views/InitGitFlow.axaml | 2 +- src/Views/Launcher.axaml | 2 +- src/Views/Launcher.axaml.cs | 16 +-- src/Views/LauncherPage.axaml | 103 +++++++++--------- src/Views/LauncherTabBar.axaml.cs | 16 ++- src/Views/MergeConflictEditor.axaml.cs | 2 +- src/Views/MoveSubmodule.axaml | 2 +- src/Views/Preferences.axaml | 10 +- src/Views/RenameBranch.axaml | 2 +- src/Views/Repository.axaml | 4 +- src/Views/RepositoryConfigure.axaml | 8 +- src/Views/RepositoryToolbar.axaml.cs | 2 +- src/Views/RevisionCompare.axaml | 2 +- src/Views/RevisionFileContentViewer.axaml.cs | 1 + src/Views/RevisionFiles.axaml | 2 +- src/Views/SetSubmoduleBranch.axaml | 2 +- src/Views/StashChanges.axaml | 2 +- src/Views/SubmoduleRevisionCompare.axaml | 2 +- src/Views/TextDiffView.axaml.cs | 4 +- src/Views/Welcome.axaml | 2 +- src/Views/Welcome.axaml.cs | 23 ++-- 55 files changed, 196 insertions(+), 299 deletions(-) diff --git a/depends/AvaloniaEdit b/depends/AvaloniaEdit index 1e6f595ff..8e0290093 160000 --- a/depends/AvaloniaEdit +++ b/depends/AvaloniaEdit @@ -1 +1 @@ -Subproject commit 1e6f595ff0da0dc20637322cbab9531892c12cbc +Subproject commit 8e02900934666adc9ac16ca724dfade7d6b15aad diff --git a/src/App.axaml.cs b/src/App.axaml.cs index 8d6bafb71..40c6adc6e 100644 --- a/src/App.axaml.cs +++ b/src/App.axaml.cs @@ -9,7 +9,6 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Data.Core.Plugins; using Avalonia.Markup.Xaml; using Avalonia.Media; using Avalonia.Media.Fonts; @@ -153,8 +152,6 @@ public static void SetTheme(string theme, string themeOverridesFile) else Models.CommitGraph.SetDefaultPens(overrides.GraphPenThickness); - Native.OS.UseMicaOnWindows11 = overrides.UseMicaOnWindows11; - app.Resources.MergedDictionaries.Add(resDic); app._themeOverrides = resDic; } @@ -259,8 +256,6 @@ public override void OnFrameworkInitializationCompleted() { if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { - BindingPlugins.DataValidators.RemoveAt(0); - // Disable tooltip if window is not active. ToolTip.ToolTipOpeningEvent.AddClassHandler((c, e) => { diff --git a/src/Models/ThemeOverrides.cs b/src/Models/ThemeOverrides.cs index 531cbccdd..ccd9f57e8 100644 --- a/src/Models/ThemeOverrides.cs +++ b/src/Models/ThemeOverrides.cs @@ -9,7 +9,6 @@ public class ThemeOverrides public Dictionary BasicColors { get; set; } = new Dictionary(); public double GraphPenThickness { get; set; } = 2; public double OpacityForNotMergedCommits { get; set; } = 0.5; - public bool UseMicaOnWindows11 { get; set; } = true; public List GraphColors { get; set; } = new List(); } } diff --git a/src/Native/Linux.cs b/src/Native/Linux.cs index 881c9bcf8..b03d166b9 100644 --- a/src/Native/Linux.cs +++ b/src/Native/Linux.cs @@ -6,7 +6,6 @@ using Avalonia; using Avalonia.Controls; -using Avalonia.Platform; namespace SourceGit.Native { @@ -22,13 +21,13 @@ public void SetupWindow(Window window) { if (OS.UseSystemWindowFrame) { - window.ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.Default; window.ExtendClientAreaToDecorationsHint = false; + window.WindowDecorations = WindowDecorations.Full; } else { - window.ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.NoChrome; window.ExtendClientAreaToDecorationsHint = true; + window.WindowDecorations = WindowDecorations.None; window.Classes.Add("custom_window_frame"); } } diff --git a/src/Native/MacOS.cs b/src/Native/MacOS.cs index fe0c04753..782f72046 100644 --- a/src/Native/MacOS.cs +++ b/src/Native/MacOS.cs @@ -6,7 +6,6 @@ using Avalonia; using Avalonia.Controls; -using Avalonia.Platform; namespace SourceGit.Native { @@ -40,8 +39,8 @@ public void SetupApp(AppBuilder builder) public void SetupWindow(Window window) { - window.ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.SystemChrome; window.ExtendClientAreaToDecorationsHint = true; + window.WindowDecorations = WindowDecorations.Full; } public string GetDataDir() diff --git a/src/Native/OS.cs b/src/Native/OS.cs index 7c12287ac..f2e7e38da 100644 --- a/src/Native/OS.cs +++ b/src/Native/OS.cs @@ -108,12 +108,6 @@ public static string ExternalDiffArgs set; } = string.Empty; - public static bool UseMicaOnWindows11 - { - get => OperatingSystem.IsWindows() && OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22000) && _enableMicaOnWindows11; - set => _enableMicaOnWindows11 = value; - } - public static bool UseSystemWindowFrame { get => OperatingSystem.IsLinux() && _enableSystemWindowFrame; @@ -330,6 +324,5 @@ private static void UpdateGitVersion() private static IBackend _backend = null; private static string _gitExecutable = string.Empty; private static bool _enableSystemWindowFrame = false; - private static bool _enableMicaOnWindows11 = true; } } diff --git a/src/Native/Windows.cs b/src/Native/Windows.cs index e71c0de71..51d99de5b 100644 --- a/src/Native/Windows.cs +++ b/src/Native/Windows.cs @@ -9,26 +9,12 @@ using Avalonia; using Avalonia.Controls; -using Avalonia.Platform; -using Avalonia.Threading; namespace SourceGit.Native { [SupportedOSPlatform("windows")] internal class Windows : OS.IBackend { - [StructLayout(LayoutKind.Sequential)] - internal struct MARGINS - { - public int cxLeftWidth; - public int cxRightWidth; - public int cyTopHeight; - public int cyBottomHeight; - } - - [DllImport("dwmapi.dll")] - private static extern int DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS margins); - [DllImport("shlwapi.dll", CharSet = CharSet.Unicode, SetLastError = false)] private static extern bool PathFindOnPath([In, Out] StringBuilder pszFile, [In] string[] ppszOtherDirs); @@ -43,19 +29,13 @@ internal struct MARGINS public void SetupApp(AppBuilder builder) { - // Fix drop shadow issue on Windows 10 - if (!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22000)) - { - Window.WindowStateProperty.Changed.AddClassHandler((w, _) => FixWindowFrameOnWin10(w)); - Control.LoadedEvent.AddClassHandler((w, _) => FixWindowFrameOnWin10(w)); - } + // Do nothing for now. } public void SetupWindow(Window window) { - window.ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.NoChrome; + window.WindowDecorations = WindowDecorations.BorderOnly; window.ExtendClientAreaToDecorationsHint = true; - window.BorderThickness = new Thickness(1); } public string GetDataDir() @@ -212,21 +192,6 @@ public void OpenWithDefaultEditor(string file) } #region HELPER_METHODS - private void FixWindowFrameOnWin10(Window w) - { - // Schedule the DWM frame extension to run in the next render frame - // to ensure proper timing with the window initialization sequence - Dispatcher.UIThread.Post(() => - { - var platformHandle = w.TryGetPlatformHandle(); - if (platformHandle == null) - return; - - var margins = new MARGINS { cxLeftWidth = 1, cxRightWidth = 1, cyTopHeight = 1, cyBottomHeight = 1 }; - DwmExtendFrameIntoClientArea(platformHandle.Handle, ref margins); - }, DispatcherPriority.Render); - } - private List GenerateVSProjectLaunchOptions(string path) { var root = new DirectoryInfo(path); diff --git a/src/Resources/Styles.axaml b/src/Resources/Styles.axaml index 0c4dbb7fb..4139d931b 100644 --- a/src/Resources/Styles.axaml +++ b/src/Resources/Styles.axaml @@ -19,45 +19,39 @@ - - - + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + +