diff --git a/src/Models/ExternalTool.cs b/src/Models/ExternalTool.cs index f453c08f..bd04f2f9 100644 --- a/src/Models/ExternalTool.cs +++ b/src/Models/ExternalTool.cs @@ -20,7 +20,7 @@ namespace SourceGit.Models { Name = name; ExecFile = execFile; - _execArgsGenerator = execArgsGenerator ?? (repo => repo.Quoted()); + _execArgsGenerator = execArgsGenerator ?? (path => path.Quoted()); try { @@ -34,13 +34,12 @@ namespace SourceGit.Models } } - public void Open(string repo) + public void Open(string path) { Process.Start(new ProcessStartInfo() { - WorkingDirectory = repo, FileName = ExecFile, - Arguments = _execArgsGenerator.Invoke(repo), + Arguments = _execArgsGenerator.Invoke(path), UseShellExecute = false, }); } diff --git a/src/Native/Windows.cs b/src/Native/Windows.cs index 8439d702..70e01796 100644 --- a/src/Native/Windows.cs +++ b/src/Native/Windows.cs @@ -449,10 +449,15 @@ namespace SourceGit.Native } } - private string GenerateCommandlineArgsForVisualStudio(string repo) + private string GenerateCommandlineArgsForVisualStudio(string path) { - var sln = FindVSSolutionFile(new DirectoryInfo(repo), 4); - return string.IsNullOrEmpty(sln) ? repo.Quoted() : sln.Quoted(); + if (Directory.Exists(path)) + { + var sln = FindVSSolutionFile(new DirectoryInfo(path), 4); + return string.IsNullOrEmpty(sln) ? path.Quoted() : sln.Quoted(); + } + + return path.Quoted(); } private string FindVSSolutionFile(DirectoryInfo dir, int leftDepth) diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index 9eb5fe94..e204baac 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -536,9 +536,10 @@ Select parent node for: Name: Git has NOT been configured. Please to go [Preferences] and configure it first. + Open + Default Editor (System) Open Data Storage Directory Open in Merge Tool - Open with... Optional. Create New Tab Bookmark diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 242373ad..c4dbdc05 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -540,9 +540,10 @@ 请选择目标分组: 名称 : GIT尚未配置。请打开【偏好设置】配置GIT路径。 + 打开 + 系统默认编辑器 浏览应用数据目录 使用外部对比工具查看 - 打开文件... 选填。 新建空白页 设置书签 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index 1ddc9f16..c46bb316 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -540,9 +540,10 @@ 請選擇目標分組: 名稱: 尚未設定 Git。請開啟 [偏好設定] 以設定 Git 路徑。 + 開啟 + 系統預設編輯器 瀏覽程式資料目錄 使用外部比對工具檢視 - 開啟檔案... 選填。 新增分頁 設定書籤 diff --git a/src/ViewModels/CommitDetail.cs b/src/ViewModels/CommitDetail.cs index c272689d..119ae753 100644 --- a/src/ViewModels/CommitDetail.cs +++ b/src/ViewModels/CommitDetail.cs @@ -318,7 +318,7 @@ namespace SourceGit.ViewModels } } - public async Task OpenRevisionFileWithDefaultEditorAsync(string file) + public async Task OpenRevisionFileAsync(string file, Models.ExternalTool tool) { var fullPath = Native.OS.GetAbsPath(_repo.FullPath, file); var fileName = Path.GetFileNameWithoutExtension(fullPath) ?? ""; @@ -329,7 +329,10 @@ namespace SourceGit.ViewModels .RunAsync(_repo.FullPath, _commit.SHA, file, tmpFile) .ConfigureAwait(false); - Native.OS.OpenWithDefaultEditor(tmpFile); + if (tool == null) + Native.OS.OpenWithDefaultEditor(tmpFile); + else + tool.Open(tmpFile); } public async Task SaveRevisionFileAsync(Models.Object file, string saveTo) diff --git a/src/ViewModels/WorkingCopy.cs b/src/ViewModels/WorkingCopy.cs index 1cc24429..78d069d0 100644 --- a/src/ViewModels/WorkingCopy.cs +++ b/src/ViewModels/WorkingCopy.cs @@ -335,13 +335,6 @@ namespace SourceGit.ViewModels }); } - public void OpenWithDefaultEditor(Models.Change c) - { - var absPath = Native.OS.GetAbsPath(_repo.FullPath, c.Path); - if (File.Exists(absPath)) - Native.OS.OpenWithDefaultEditor(absPath); - } - public async Task StageChangesAsync(List changes, Models.Change next) { var canStaged = await GetCanStageChangesAsync(changes); diff --git a/src/Views/CommitDetail.axaml.cs b/src/Views/CommitDetail.axaml.cs index 9dbfbe13..7bba82e6 100644 --- a/src/Views/CommitDetail.axaml.cs +++ b/src/Views/CommitDetail.axaml.cs @@ -209,6 +209,44 @@ namespace SourceGit.Views if (DataContext is not ViewModels.CommitDetail { Repository: { } repo, Commit: { } commit } vm) return null; + var openWith = new MenuItem(); + openWith.Header = App.Text("Open"); + openWith.Icon = App.CreateMenuIcon("Icons.OpenWith"); + openWith.IsEnabled = change.Index != Models.ChangeState.Deleted; + if (openWith.IsEnabled) + { + var defaultEditor = new MenuItem(); + defaultEditor.Header = App.Text("Open.SystemDefaultEditor"); + defaultEditor.Click += async (_, ev) => + { + await vm.OpenRevisionFileAsync(change.Path, null); + ev.Handled = true; + }; + + openWith.Items.Add(defaultEditor); + + var tools = Native.OS.ExternalTools; + if (tools.Count > 0) + { + openWith.Items.Add(new MenuItem() { Header = "-" }); + + for (var i = 0; i < tools.Count; i++) + { + var tool = tools[i]; + var item = new MenuItem(); + item.Header = tool.Name; + item.Icon = new Image { Width = 16, Height = 16, Source = tool.IconImage }; + item.Click += async (_, ev) => + { + await vm.OpenRevisionFileAsync(change.Path, tool); + ev.Handled = true; + }; + + openWith.Items.Add(item); + } + } + } + var openWithMerger = new MenuItem(); openWithMerger.Header = App.Text("OpenInExternalMergeTool"); openWithMerger.Icon = App.CreateMenuIcon("Icons.OpenWith"); @@ -219,16 +257,6 @@ namespace SourceGit.Views ev.Handled = true; }; - var openWith = new MenuItem(); - openWith.Header = App.Text("OpenWith"); - openWith.Icon = App.CreateMenuIcon("Icons.OpenWith"); - openWith.IsEnabled = change.Index != Models.ChangeState.Deleted; - openWith.Click += async (_, ev) => - { - await vm.OpenRevisionFileWithDefaultEditorAsync(change.Path); - ev.Handled = true; - }; - var fullPath = Native.OS.GetAbsPath(repo.FullPath, change.Path); var explore = new MenuItem(); explore.Header = App.Text("RevealFile"); @@ -291,8 +319,8 @@ namespace SourceGit.Views }; var menu = new ContextMenu(); - menu.Items.Add(openWithMerger); menu.Items.Add(openWith); + menu.Items.Add(openWithMerger); menu.Items.Add(explore); menu.Items.Add(new MenuItem { Header = "-" }); menu.Items.Add(history); diff --git a/src/Views/FileHistories.axaml b/src/Views/FileHistories.axaml index ba296bfa..2988f2c5 100644 --- a/src/Views/FileHistories.axaml +++ b/src/Views/FileHistories.axaml @@ -169,7 +169,7 @@ Background="Transparent" Click="OnOpenFileWithDefaultEditor" IsVisible="{Binding CanOpenWithDefaultEditor, Mode=OneWay}" - ToolTip.Tip="{DynamicResource Text.OpenWith}"> + ToolTip.Tip="{DynamicResource Text.Open}"> diff --git a/src/Views/RevisionFileTreeView.axaml.cs b/src/Views/RevisionFileTreeView.axaml.cs index 2ad6b0e8..3c0721e4 100644 --- a/src/Views/RevisionFileTreeView.axaml.cs +++ b/src/Views/RevisionFileTreeView.axaml.cs @@ -495,15 +495,43 @@ namespace SourceGit.Views var menu = new ContextMenu(); var openWith = new MenuItem(); - openWith.Header = App.Text("OpenWith"); + openWith.Header = App.Text("Open"); openWith.Icon = App.CreateMenuIcon("Icons.OpenWith"); - openWith.Tag = OperatingSystem.IsMacOS() ? "⌘+O" : "Ctrl+O"; openWith.IsEnabled = file.Type == Models.ObjectType.Blob; - openWith.Click += async (_, ev) => + if (openWith.IsEnabled) { - await vm.OpenRevisionFileWithDefaultEditorAsync(file.Path); - ev.Handled = true; - }; + var defaultEditor = new MenuItem(); + defaultEditor.Header = App.Text("Open.SystemDefaultEditor"); + defaultEditor.Tag = OperatingSystem.IsMacOS() ? "⌘+O" : "Ctrl+O"; + defaultEditor.Click += async (_, ev) => + { + await vm.OpenRevisionFileAsync(file.Path, null); + ev.Handled = true; + }; + + openWith.Items.Add(defaultEditor); + + var tools = Native.OS.ExternalTools; + if (tools.Count > 0) + { + openWith.Items.Add(new MenuItem() { Header = "-" }); + + for (var i = 0; i < tools.Count; i++) + { + var tool = tools[i]; + var item = new MenuItem(); + item.Header = tool.Name; + item.Icon = new Image { Width = 16, Height = 16, Source = tool.IconImage }; + item.Click += async (_, ev) => + { + await vm.OpenRevisionFileAsync(file.Path, tool); + ev.Handled = true; + }; + + openWith.Items.Add(item); + } + } + } var saveAs = new MenuItem(); saveAs.Header = App.Text("SaveAs"); diff --git a/src/Views/RevisionFiles.axaml b/src/Views/RevisionFiles.axaml index 4144f3ed..7c1c9912 100644 --- a/src/Views/RevisionFiles.axaml +++ b/src/Views/RevisionFiles.axaml @@ -151,7 +151,7 @@ HotKey="{OnPlatform Ctrl+O, macOS=⌘+O}"> - + diff --git a/src/Views/RevisionFiles.axaml.cs b/src/Views/RevisionFiles.axaml.cs index dd51943c..a4a01b65 100644 --- a/src/Views/RevisionFiles.axaml.cs +++ b/src/Views/RevisionFiles.axaml.cs @@ -82,7 +82,7 @@ namespace SourceGit.Views private async void OnOpenFileWithDefaultEditor(object sender, RoutedEventArgs e) { if (DataContext is ViewModels.CommitDetail { CanOpenRevisionFileWithDefaultEditor: true } vm) - await vm.OpenRevisionFileWithDefaultEditorAsync(vm.ViewRevisionFilePath); + await vm.OpenRevisionFileAsync(vm.ViewRevisionFilePath, null); e.Handled = true; } diff --git a/src/Views/WorkingCopy.axaml.cs b/src/Views/WorkingCopy.axaml.cs index 4f88a169..b786b0d9 100644 --- a/src/Views/WorkingCopy.axaml.cs +++ b/src/Views/WorkingCopy.axaml.cs @@ -110,7 +110,10 @@ namespace SourceGit.Views e.KeyModifiers == (OperatingSystem.IsMacOS() ? KeyModifiers.Meta : KeyModifiers.Control) && vm.SelectedUnstaged is { Count: 1 }) { - vm.OpenWithDefaultEditor(vm.SelectedUnstaged[0]); + var change = vm.SelectedUnstaged[0]; + var fullpath = Native.OS.GetAbsPath(vm.Repository.FullPath, change.Path); + if (File.Exists(fullpath)) + Native.OS.OpenWithDefaultEditor(fullpath); e.Handled = true; } else if (e.Key is Key.C && @@ -143,7 +146,10 @@ namespace SourceGit.Views e.KeyModifiers == (OperatingSystem.IsMacOS() ? KeyModifiers.Meta : KeyModifiers.Control) && vm.SelectedStaged is { Count: 1 }) { - vm.OpenWithDefaultEditor(vm.SelectedStaged[0]); + var change = vm.SelectedStaged[0]; + var fullpath = Native.OS.GetAbsPath(vm.Repository.FullPath, change.Path); + if (File.Exists(fullpath)) + Native.OS.OpenWithDefaultEditor(fullpath); e.Handled = true; } else if (e.Key is Key.C && @@ -254,6 +260,7 @@ namespace SourceGit.Views { var change = selectedUnstaged[0]; var path = Native.OS.GetAbsPath(repo.FullPath, change.Path); + TryAddOpenFileToContextMenu(menu, path); if (!change.IsConflicted || change.ConflictReason is Models.ConflictReason.BothAdded or Models.ConflictReason.BothModified) { @@ -273,18 +280,6 @@ namespace SourceGit.Views menu.Items.Add(openMerger); } - var openWith = new MenuItem(); - openWith.Header = App.Text("OpenWith"); - openWith.Icon = App.CreateMenuIcon("Icons.OpenWith"); - openWith.Tag = OperatingSystem.IsMacOS() ? "⌘+O" : "Ctrl+O"; - openWith.IsEnabled = File.Exists(path); - openWith.Click += (_, e) => - { - vm.OpenWithDefaultEditor(change); - e.Handled = true; - }; - menu.Items.Add(openWith); - var explore = new MenuItem(); explore.Header = App.Text("RevealFile"); explore.Icon = App.CreateMenuIcon("Icons.Explore"); @@ -934,17 +929,6 @@ namespace SourceGit.Views ev.Handled = true; }; - var openWith = new MenuItem(); - openWith.Header = App.Text("OpenWith"); - openWith.Icon = App.CreateMenuIcon("Icons.OpenWith"); - openWith.Tag = OperatingSystem.IsMacOS() ? "⌘+O" : "Ctrl+O"; - openWith.IsEnabled = File.Exists(path); - openWith.Click += (_, e) => - { - vm.OpenWithDefaultEditor(change); - e.Handled = true; - }; - var explore = new MenuItem(); explore.IsEnabled = File.Exists(path) || Directory.Exists(path); explore.Header = App.Text("RevealFile"); @@ -1005,8 +989,8 @@ namespace SourceGit.Views e.Handled = true; }; + TryAddOpenFileToContextMenu(menu, path); menu.Items.Add(openWithMerger); - menu.Items.Add(openWith); menu.Items.Add(explore); menu.Items.Add(new MenuItem() { Header = "-" }); menu.Items.Add(unstage); @@ -1274,6 +1258,49 @@ namespace SourceGit.Views return menu; } + private void TryAddOpenFileToContextMenu(ContextMenu menu, string fullpath) + { + var openWith = new MenuItem(); + openWith.Header = App.Text("Open"); + openWith.Icon = App.CreateMenuIcon("Icons.OpenWith"); + openWith.IsEnabled = File.Exists(fullpath); + if (openWith.IsEnabled) + { + var defaultEditor = new MenuItem(); + defaultEditor.Header = App.Text("Open.SystemDefaultEditor"); + defaultEditor.Tag = OperatingSystem.IsMacOS() ? "⌘+O" : "Ctrl+O"; + defaultEditor.Click += (_, ev) => + { + Native.OS.OpenWithDefaultEditor(fullpath); + ev.Handled = true; + }; + + openWith.Items.Add(defaultEditor); + + var tools = Native.OS.ExternalTools; + if (tools.Count > 0) + { + openWith.Items.Add(new MenuItem() { Header = "-" }); + + for (var i = 0; i < tools.Count; i++) + { + var tool = tools[i]; + var item = new MenuItem(); + item.Header = tool.Name; + item.Icon = new Image { Width = 16, Height = 16, Source = tool.IconImage }; + item.Click += (_, e) => + { + tool.Open(fullpath); + e.Handled = true; + }; + + openWith.Items.Add(item); + } + } + } + menu.Items.Add(openWith); + } + private void TryToAddCustomActionsToContextMenu(ViewModels.Repository repo, ContextMenu menu, string path) { var actions = repo.GetCustomActions(Models.CustomActionScope.File);