mirror of
https://fastgit.cc/github.com/sourcegit-scm/sourcegit
synced 2026-04-23 10:22:13 +08:00
refactor: move context menu creation from ViewModels to Views (PART 8)
Signed-off-by: leo <longshuang@msn.cn>
This commit is contained in:
@@ -5,16 +5,18 @@ using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.Threading;
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace SourceGit.ViewModels
|
||||
{
|
||||
public partial class CommitDetail : ObservableObject, IDisposable
|
||||
{
|
||||
public Repository Repository
|
||||
{
|
||||
get => _repo;
|
||||
}
|
||||
|
||||
public int ActivePageIndex
|
||||
{
|
||||
get => _rememberActivePageIndex ? _repo.CommitDetailActivePageIndex : _activePageIndex;
|
||||
@@ -198,6 +200,48 @@ namespace SourceGit.ViewModels
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public string GetAbsPath(string path)
|
||||
{
|
||||
return Native.OS.GetAbsPath(_repo.FullPath, path);
|
||||
}
|
||||
|
||||
public void OpenChangeInMergeTool(Models.Change c)
|
||||
{
|
||||
var toolType = Preferences.Instance.ExternalMergeToolType;
|
||||
var toolPath = Preferences.Instance.ExternalMergeToolPath;
|
||||
var opt = new Models.DiffOption(_commit, c);
|
||||
new Commands.DiffTool(_repo.FullPath, toolType, toolPath, opt).Open();
|
||||
}
|
||||
|
||||
public async Task SaveChangesAsPatchAsync(List<Models.Change> changes, string saveTo)
|
||||
{
|
||||
if (_commit == null)
|
||||
return;
|
||||
|
||||
var baseRevision = _commit.Parents.Count == 0 ? Models.Commit.EmptyTreeSHA1 : _commit.Parents[0];
|
||||
var succ = await Commands.SaveChangesAsPatch.ProcessRevisionCompareChangesAsync(_repo.FullPath, changes, baseRevision, _commit.SHA, saveTo);
|
||||
if (succ)
|
||||
App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess"));
|
||||
}
|
||||
|
||||
public async Task ResetToThisRevisionAsync(string path)
|
||||
{
|
||||
var log = _repo.CreateLog($"Reset File to '{_commit.SHA}'");
|
||||
await new Commands.Checkout(_repo.FullPath).Use(log).FileWithRevisionAsync(path, $"{_commit.SHA}");
|
||||
log.Complete();
|
||||
}
|
||||
|
||||
public async Task ResetToParentRevisionAsync(Models.Change change)
|
||||
{
|
||||
var log = _repo.CreateLog($"Reset File to '{_commit.SHA}~1'");
|
||||
|
||||
if (change.Index == Models.ChangeState.Renamed)
|
||||
await new Commands.Checkout(_repo.FullPath).Use(log).FileWithRevisionAsync(change.OriginalPath, $"{_commit.SHA}~1");
|
||||
|
||||
await new Commands.Checkout(_repo.FullPath).Use(log).FileWithRevisionAsync(change.Path, $"{_commit.SHA}~1");
|
||||
log.Complete();
|
||||
}
|
||||
|
||||
public async Task<List<Models.Object>> GetRevisionFilesUnderFolderAsync(string parentFolder)
|
||||
{
|
||||
return await new Commands.QueryRevisionObjects(_repo.FullPath, _commit.SHA, parentFolder)
|
||||
@@ -241,429 +285,11 @@ namespace SourceGit.ViewModels
|
||||
Native.OS.OpenWithDefaultEditor(tmpFile);
|
||||
}
|
||||
|
||||
public string GetAbsPath(string path)
|
||||
public async Task SaveRevisionFileAsync(Models.Object file, string saveTo)
|
||||
{
|
||||
return Native.OS.GetAbsPath(_repo.FullPath, path);
|
||||
}
|
||||
|
||||
public void OpenChangeInMergeTool(Models.Change c)
|
||||
{
|
||||
var toolType = Preferences.Instance.ExternalMergeToolType;
|
||||
var toolPath = Preferences.Instance.ExternalMergeToolPath;
|
||||
var opt = new Models.DiffOption(_commit, c);
|
||||
new Commands.DiffTool(_repo.FullPath, toolType, toolPath, opt).Open();
|
||||
}
|
||||
|
||||
public async Task SaveRevisionFile(Models.Object file)
|
||||
{
|
||||
var storageProvider = App.GetStorageProvider();
|
||||
if (storageProvider == null)
|
||||
return;
|
||||
|
||||
var options = new FolderPickerOpenOptions() { AllowMultiple = false };
|
||||
try
|
||||
{
|
||||
var selected = await 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 saveTo = Path.Combine(folderPath, Path.GetFileName(file.Path)!);
|
||||
|
||||
await Commands.SaveRevisionFile
|
||||
.RunAsync(_repo.FullPath, _commit.SHA, file.Path, saveTo)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
App.RaiseException(_repo.FullPath, $"Failed to save file: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public ContextMenu CreateChangeContextMenuByFolder(ChangeTreeNode node, List<Models.Change> changes)
|
||||
{
|
||||
var fullPath = Native.OS.GetAbsPath(_repo.FullPath, node.FullPath);
|
||||
var explore = new MenuItem();
|
||||
explore.Header = App.Text("RevealFile");
|
||||
explore.Icon = App.CreateMenuIcon("Icons.Explore");
|
||||
explore.IsEnabled = Directory.Exists(fullPath);
|
||||
explore.Click += (_, ev) =>
|
||||
{
|
||||
Native.OS.OpenInFileManager(fullPath, true);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var history = new MenuItem();
|
||||
history.Header = App.Text("DirHistories");
|
||||
history.Icon = App.CreateMenuIcon("Icons.Histories");
|
||||
history.Click += (_, ev) =>
|
||||
{
|
||||
App.ShowWindow(new DirHistories(_repo, node.FullPath, _commit.SHA));
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var patch = new MenuItem();
|
||||
patch.Header = App.Text("FileCM.SaveAsPatch");
|
||||
patch.Icon = App.CreateMenuIcon("Icons.Diff");
|
||||
patch.Click += async (_, e) =>
|
||||
{
|
||||
var storageProvider = App.GetStorageProvider();
|
||||
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"] }];
|
||||
|
||||
var baseRevision = _commit.Parents.Count == 0 ? Models.Commit.EmptyTreeSHA1 : _commit.Parents[0];
|
||||
var storageFile = await storageProvider.SaveFilePickerAsync(options);
|
||||
if (storageFile != null)
|
||||
{
|
||||
var saveTo = storageFile.Path.LocalPath;
|
||||
var succ = await Commands.SaveChangesAsPatch.ProcessRevisionCompareChangesAsync(_repo.FullPath, changes, baseRevision, _commit.SHA, saveTo);
|
||||
if (succ)
|
||||
App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess"));
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
var copyPath = new MenuItem();
|
||||
copyPath.Header = App.Text("CopyPath");
|
||||
copyPath.Icon = App.CreateMenuIcon("Icons.Copy");
|
||||
copyPath.Tag = OperatingSystem.IsMacOS() ? "⌘+C" : "Ctrl+C";
|
||||
copyPath.Click += async (_, ev) =>
|
||||
{
|
||||
await App.CopyTextAsync(node.FullPath);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var copyFullPath = new MenuItem();
|
||||
copyFullPath.Header = App.Text("CopyFullPath");
|
||||
copyFullPath.Icon = App.CreateMenuIcon("Icons.Copy");
|
||||
copyFullPath.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+C" : "Ctrl+Shift+C";
|
||||
copyFullPath.Click += async (_, e) =>
|
||||
{
|
||||
await App.CopyTextAsync(fullPath);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
var menu = new ContextMenu();
|
||||
menu.Items.Add(explore);
|
||||
menu.Items.Add(new MenuItem { Header = "-" });
|
||||
menu.Items.Add(history);
|
||||
menu.Items.Add(patch);
|
||||
menu.Items.Add(new MenuItem { Header = "-" });
|
||||
menu.Items.Add(copyPath);
|
||||
menu.Items.Add(copyFullPath);
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
public ContextMenu CreateChangeContextMenu(Models.Change change)
|
||||
{
|
||||
var openWithMerger = new MenuItem();
|
||||
openWithMerger.Header = App.Text("OpenInExternalMergeTool");
|
||||
openWithMerger.Icon = App.CreateMenuIcon("Icons.OpenWith");
|
||||
openWithMerger.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+D" : "Ctrl+Shift+D";
|
||||
openWithMerger.Click += (_, ev) =>
|
||||
{
|
||||
OpenChangeInMergeTool(change);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var fullPath = Native.OS.GetAbsPath(_repo.FullPath, change.Path);
|
||||
var explore = new MenuItem();
|
||||
explore.Header = App.Text("RevealFile");
|
||||
explore.Icon = App.CreateMenuIcon("Icons.Explore");
|
||||
explore.IsEnabled = File.Exists(fullPath);
|
||||
explore.Click += (_, ev) =>
|
||||
{
|
||||
Native.OS.OpenInFileManager(fullPath, true);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var history = new MenuItem();
|
||||
history.Header = App.Text("FileHistory");
|
||||
history.Icon = App.CreateMenuIcon("Icons.Histories");
|
||||
history.Click += (_, ev) =>
|
||||
{
|
||||
App.ShowWindow(new FileHistories(_repo, change.Path, _commit.SHA));
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var blame = new MenuItem();
|
||||
blame.Header = App.Text("Blame");
|
||||
blame.Icon = App.CreateMenuIcon("Icons.Blame");
|
||||
blame.IsEnabled = change.Index != Models.ChangeState.Deleted;
|
||||
blame.Click += (_, ev) =>
|
||||
{
|
||||
App.ShowWindow(new Blame(_repo.FullPath, change.Path, _commit));
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var patch = new MenuItem();
|
||||
patch.Header = App.Text("FileCM.SaveAsPatch");
|
||||
patch.Icon = App.CreateMenuIcon("Icons.Diff");
|
||||
patch.Click += async (_, e) =>
|
||||
{
|
||||
var storageProvider = App.GetStorageProvider();
|
||||
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"] }];
|
||||
|
||||
var baseRevision = _commit.Parents.Count == 0 ? Models.Commit.EmptyTreeSHA1 : _commit.Parents[0];
|
||||
var storageFile = await storageProvider.SaveFilePickerAsync(options);
|
||||
if (storageFile != null)
|
||||
{
|
||||
var saveTo = storageFile.Path.LocalPath;
|
||||
var succ = await Commands.SaveChangesAsPatch.ProcessRevisionCompareChangesAsync(_repo.FullPath, [change], baseRevision, _commit.SHA, saveTo);
|
||||
if (succ)
|
||||
App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess"));
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
var menu = new ContextMenu();
|
||||
menu.Items.Add(openWithMerger);
|
||||
menu.Items.Add(explore);
|
||||
menu.Items.Add(new MenuItem { Header = "-" });
|
||||
menu.Items.Add(history);
|
||||
menu.Items.Add(blame);
|
||||
menu.Items.Add(patch);
|
||||
menu.Items.Add(new MenuItem { Header = "-" });
|
||||
|
||||
if (!_repo.IsBare)
|
||||
{
|
||||
var resetToThisRevision = new MenuItem();
|
||||
resetToThisRevision.Header = App.Text("ChangeCM.CheckoutThisRevision");
|
||||
resetToThisRevision.Icon = App.CreateMenuIcon("Icons.File.Checkout");
|
||||
resetToThisRevision.Click += async (_, ev) =>
|
||||
{
|
||||
await ResetToThisRevisionAsync(change.Path);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var resetToFirstParent = new MenuItem();
|
||||
resetToFirstParent.Header = App.Text("ChangeCM.CheckoutFirstParentRevision");
|
||||
resetToFirstParent.Icon = App.CreateMenuIcon("Icons.File.Checkout");
|
||||
resetToFirstParent.IsEnabled = _commit.Parents.Count > 0;
|
||||
resetToFirstParent.Click += async (_, ev) =>
|
||||
{
|
||||
await ResetToParentRevisionAsync(change);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
menu.Items.Add(resetToThisRevision);
|
||||
menu.Items.Add(resetToFirstParent);
|
||||
menu.Items.Add(new MenuItem { Header = "-" });
|
||||
|
||||
TryToAddContextMenuItemsForGitLFS(menu, fullPath, change.Path);
|
||||
}
|
||||
|
||||
var copyPath = new MenuItem();
|
||||
copyPath.Header = App.Text("CopyPath");
|
||||
copyPath.Icon = App.CreateMenuIcon("Icons.Copy");
|
||||
copyPath.Tag = OperatingSystem.IsMacOS() ? "⌘+C" : "Ctrl+C";
|
||||
copyPath.Click += async (_, ev) =>
|
||||
{
|
||||
await App.CopyTextAsync(change.Path);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var copyFullPath = new MenuItem();
|
||||
copyFullPath.Header = App.Text("CopyFullPath");
|
||||
copyFullPath.Icon = App.CreateMenuIcon("Icons.Copy");
|
||||
copyFullPath.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+C" : "Ctrl+Shift+C";
|
||||
copyFullPath.Click += async (_, e) =>
|
||||
{
|
||||
await App.CopyTextAsync(fullPath);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
menu.Items.Add(copyPath);
|
||||
menu.Items.Add(copyFullPath);
|
||||
return menu;
|
||||
}
|
||||
|
||||
public ContextMenu CreateRevisionFileContextMenuByFolder(string path)
|
||||
{
|
||||
var fullPath = Native.OS.GetAbsPath(_repo.FullPath, path);
|
||||
var explore = new MenuItem();
|
||||
explore.Header = App.Text("RevealFile");
|
||||
explore.Icon = App.CreateMenuIcon("Icons.Explore");
|
||||
explore.IsEnabled = Directory.Exists(fullPath);
|
||||
explore.Click += (_, ev) =>
|
||||
{
|
||||
Native.OS.OpenInFileManager(fullPath, true);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var history = new MenuItem();
|
||||
history.Header = App.Text("DirHistories");
|
||||
history.Icon = App.CreateMenuIcon("Icons.Histories");
|
||||
history.Click += (_, ev) =>
|
||||
{
|
||||
App.ShowWindow(new DirHistories(_repo, path, _commit.SHA));
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var copyPath = new MenuItem();
|
||||
copyPath.Header = App.Text("CopyPath");
|
||||
copyPath.Icon = App.CreateMenuIcon("Icons.Copy");
|
||||
copyPath.Tag = OperatingSystem.IsMacOS() ? "⌘+C" : "Ctrl+C";
|
||||
copyPath.Click += async (_, ev) =>
|
||||
{
|
||||
await App.CopyTextAsync(path);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var copyFullPath = new MenuItem();
|
||||
copyFullPath.Header = App.Text("CopyFullPath");
|
||||
copyFullPath.Icon = App.CreateMenuIcon("Icons.Copy");
|
||||
copyFullPath.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+C" : "Ctrl+Shift+C";
|
||||
copyFullPath.Click += async (_, e) =>
|
||||
{
|
||||
await App.CopyTextAsync(fullPath);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
var menu = new ContextMenu();
|
||||
menu.Items.Add(explore);
|
||||
menu.Items.Add(new MenuItem() { Header = "-" });
|
||||
menu.Items.Add(history);
|
||||
menu.Items.Add(new MenuItem() { Header = "-" });
|
||||
menu.Items.Add(copyPath);
|
||||
menu.Items.Add(copyFullPath);
|
||||
return menu;
|
||||
}
|
||||
|
||||
public ContextMenu CreateRevisionFileContextMenu(Models.Object file)
|
||||
{
|
||||
if (file.Type == Models.ObjectType.Tree)
|
||||
return CreateRevisionFileContextMenuByFolder(file.Path);
|
||||
|
||||
var menu = new ContextMenu();
|
||||
var fullPath = Native.OS.GetAbsPath(_repo.FullPath, file.Path);
|
||||
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.Type == Models.ObjectType.Blob;
|
||||
openWith.Click += async (_, ev) =>
|
||||
{
|
||||
await OpenRevisionFileWithDefaultEditorAsync(file.Path);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var saveAs = new MenuItem();
|
||||
saveAs.Header = App.Text("SaveAs");
|
||||
saveAs.Icon = App.CreateMenuIcon("Icons.Save");
|
||||
saveAs.IsEnabled = file.Type == Models.ObjectType.Blob;
|
||||
saveAs.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+S" : "Ctrl+Shift+S";
|
||||
saveAs.Click += async (_, ev) =>
|
||||
{
|
||||
await SaveRevisionFile(file);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var explore = new MenuItem();
|
||||
explore.Header = App.Text("RevealFile");
|
||||
explore.Icon = App.CreateMenuIcon("Icons.Explore");
|
||||
explore.IsEnabled = File.Exists(fullPath);
|
||||
explore.Click += (_, ev) =>
|
||||
{
|
||||
Native.OS.OpenInFileManager(fullPath, file.Type == Models.ObjectType.Blob);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
menu.Items.Add(openWith);
|
||||
menu.Items.Add(saveAs);
|
||||
menu.Items.Add(explore);
|
||||
menu.Items.Add(new MenuItem() { Header = "-" });
|
||||
|
||||
var history = new MenuItem();
|
||||
history.Header = App.Text("FileHistory");
|
||||
history.Icon = App.CreateMenuIcon("Icons.Histories");
|
||||
history.Click += (_, ev) =>
|
||||
{
|
||||
App.ShowWindow(new FileHistories(_repo, file.Path, _commit.SHA));
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var blame = new MenuItem();
|
||||
blame.Header = App.Text("Blame");
|
||||
blame.Icon = App.CreateMenuIcon("Icons.Blame");
|
||||
blame.IsEnabled = file.Type == Models.ObjectType.Blob;
|
||||
blame.Click += (_, ev) =>
|
||||
{
|
||||
App.ShowWindow(new Blame(_repo.FullPath, file.Path, _commit));
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
menu.Items.Add(history);
|
||||
menu.Items.Add(blame);
|
||||
menu.Items.Add(new MenuItem() { Header = "-" });
|
||||
|
||||
if (!_repo.IsBare)
|
||||
{
|
||||
var resetToThisRevision = new MenuItem();
|
||||
resetToThisRevision.Header = App.Text("ChangeCM.CheckoutThisRevision");
|
||||
resetToThisRevision.Icon = App.CreateMenuIcon("Icons.File.Checkout");
|
||||
resetToThisRevision.Click += async (_, ev) =>
|
||||
{
|
||||
await ResetToThisRevisionAsync(file.Path);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var change = _changes.Find(x => x.Path == file.Path) ?? new Models.Change() { Index = Models.ChangeState.None, Path = file.Path };
|
||||
var resetToFirstParent = new MenuItem();
|
||||
resetToFirstParent.Header = App.Text("ChangeCM.CheckoutFirstParentRevision");
|
||||
resetToFirstParent.Icon = App.CreateMenuIcon("Icons.File.Checkout");
|
||||
resetToFirstParent.IsEnabled = _commit.Parents.Count > 0;
|
||||
resetToFirstParent.Click += async (_, ev) =>
|
||||
{
|
||||
await ResetToParentRevisionAsync(change);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
menu.Items.Add(resetToThisRevision);
|
||||
menu.Items.Add(resetToFirstParent);
|
||||
menu.Items.Add(new MenuItem() { Header = "-" });
|
||||
|
||||
TryToAddContextMenuItemsForGitLFS(menu, fullPath, file.Path);
|
||||
}
|
||||
|
||||
var copyPath = new MenuItem();
|
||||
copyPath.Header = App.Text("CopyPath");
|
||||
copyPath.Icon = App.CreateMenuIcon("Icons.Copy");
|
||||
copyPath.Tag = OperatingSystem.IsMacOS() ? "⌘+C" : "Ctrl+C";
|
||||
copyPath.Click += async (_, ev) =>
|
||||
{
|
||||
await App.CopyTextAsync(file.Path);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var copyFullPath = new MenuItem();
|
||||
copyFullPath.Header = App.Text("CopyFullPath");
|
||||
copyFullPath.Icon = App.CreateMenuIcon("Icons.Copy");
|
||||
copyFullPath.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+C" : "Ctrl+Shift+C";
|
||||
copyFullPath.Click += async (_, e) =>
|
||||
{
|
||||
await App.CopyTextAsync(fullPath);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
menu.Items.Add(copyPath);
|
||||
menu.Items.Add(copyFullPath);
|
||||
return menu;
|
||||
await Commands.SaveRevisionFile
|
||||
.RunAsync(_repo.FullPath, _commit.SHA, file.Path, saveTo)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void Refresh()
|
||||
@@ -823,75 +449,6 @@ namespace SourceGit.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
private void TryToAddContextMenuItemsForGitLFS(ContextMenu menu, string fullPath, string path)
|
||||
{
|
||||
if (_repo.Remotes.Count == 0 || !File.Exists(fullPath) || !_repo.IsLFSEnabled())
|
||||
return;
|
||||
|
||||
var lfs = new MenuItem();
|
||||
lfs.Header = App.Text("GitLFS");
|
||||
lfs.Icon = App.CreateMenuIcon("Icons.LFS");
|
||||
|
||||
var lfsLock = new MenuItem();
|
||||
lfsLock.Header = App.Text("GitLFS.Locks.Lock");
|
||||
lfsLock.Icon = App.CreateMenuIcon("Icons.Lock");
|
||||
if (_repo.Remotes.Count == 1)
|
||||
{
|
||||
lfsLock.Click += async (_, e) =>
|
||||
{
|
||||
await _repo.LockLFSFileAsync(_repo.Remotes[0].Name, path);
|
||||
e.Handled = true;
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var remote in _repo.Remotes)
|
||||
{
|
||||
var remoteName = remote.Name;
|
||||
var lockRemote = new MenuItem();
|
||||
lockRemote.Header = remoteName;
|
||||
lockRemote.Click += async (_, e) =>
|
||||
{
|
||||
await _repo.LockLFSFileAsync(remoteName, path);
|
||||
e.Handled = true;
|
||||
};
|
||||
lfsLock.Items.Add(lockRemote);
|
||||
}
|
||||
}
|
||||
lfs.Items.Add(lfsLock);
|
||||
|
||||
var lfsUnlock = new MenuItem();
|
||||
lfsUnlock.Header = App.Text("GitLFS.Locks.Unlock");
|
||||
lfsUnlock.Icon = App.CreateMenuIcon("Icons.Unlock");
|
||||
if (_repo.Remotes.Count == 1)
|
||||
{
|
||||
lfsUnlock.Click += async (_, e) =>
|
||||
{
|
||||
await _repo.UnlockLFSFileAsync(_repo.Remotes[0].Name, path, false, true);
|
||||
e.Handled = true;
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var remote in _repo.Remotes)
|
||||
{
|
||||
var remoteName = remote.Name;
|
||||
var unlockRemote = new MenuItem();
|
||||
unlockRemote.Header = remoteName;
|
||||
unlockRemote.Click += async (_, e) =>
|
||||
{
|
||||
await _repo.UnlockLFSFileAsync(remoteName, path, false, true);
|
||||
e.Handled = true;
|
||||
};
|
||||
lfsUnlock.Items.Add(unlockRemote);
|
||||
}
|
||||
}
|
||||
lfs.Items.Add(lfsUnlock);
|
||||
|
||||
menu.Items.Add(lfs);
|
||||
menu.Items.Add(new MenuItem() { Header = "-" });
|
||||
}
|
||||
|
||||
private void RefreshRevisionSearchSuggestion()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_revisionFileSearchFilter))
|
||||
@@ -1011,25 +568,6 @@ namespace SourceGit.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ResetToThisRevisionAsync(string path)
|
||||
{
|
||||
var log = _repo.CreateLog($"Reset File to '{_commit.SHA}'");
|
||||
|
||||
await new Commands.Checkout(_repo.FullPath).Use(log).FileWithRevisionAsync(path, $"{_commit.SHA}");
|
||||
log.Complete();
|
||||
}
|
||||
|
||||
private async Task ResetToParentRevisionAsync(Models.Change change)
|
||||
{
|
||||
var log = _repo.CreateLog($"Reset File to '{_commit.SHA}~1'");
|
||||
|
||||
if (change.Index == Models.ChangeState.Renamed)
|
||||
await new Commands.Checkout(_repo.FullPath).Use(log).FileWithRevisionAsync(change.OriginalPath, $"{_commit.SHA}~1");
|
||||
|
||||
await new Commands.Checkout(_repo.FullPath).Use(log).FileWithRevisionAsync(change.Path, $"{_commit.SHA}~1");
|
||||
log.Complete();
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"\b(https?://|ftp://)[\w\d\._/\-~%@()+:?&=#!]*[\w\d/]")]
|
||||
private static partial Regex REG_URL_FORMAT();
|
||||
|
||||
|
||||
@@ -17,18 +17,22 @@ namespace SourceGit.Views
|
||||
if (sender is not ChangeCollectionView view || DataContext is not ViewModels.CommitDetail vm)
|
||||
return;
|
||||
|
||||
var detailView = this.FindAncestorOfType<CommitDetail>();
|
||||
if (detailView == null)
|
||||
return;
|
||||
|
||||
var changes = view.SelectedChanges ?? [];
|
||||
var container = view.FindDescendantOfType<ChangeCollectionContainer>();
|
||||
if (container is { SelectedItems.Count: 1, SelectedItem: ViewModels.ChangeTreeNode { IsFolder: true } node })
|
||||
{
|
||||
var menu = vm.CreateChangeContextMenuByFolder(node, changes);
|
||||
var menu = detailView.CreateChangeContextMenuByFolder(node, changes);
|
||||
menu.Open(view);
|
||||
return;
|
||||
}
|
||||
|
||||
if (changes.Count == 1)
|
||||
{
|
||||
var menu = vm.CreateChangeContextMenu(changes[0]);
|
||||
var menu = detailView.CreateChangeContextMenu(changes[0]);
|
||||
menu.Open(view);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Platform.Storage;
|
||||
using SourceGit.ViewModels;
|
||||
|
||||
namespace SourceGit.Views
|
||||
{
|
||||
@@ -11,6 +15,282 @@ namespace SourceGit.Views
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public ContextMenu CreateChangeContextMenuByFolder(ChangeTreeNode node, List<Models.Change> changes)
|
||||
{
|
||||
if (DataContext is not ViewModels.CommitDetail { Repository: ViewModels.Repository repo, Commit: Models.Commit commit } vm)
|
||||
return null;
|
||||
|
||||
var fullPath = Native.OS.GetAbsPath(repo.FullPath, node.FullPath);
|
||||
var explore = new MenuItem();
|
||||
explore.Header = App.Text("RevealFile");
|
||||
explore.Icon = App.CreateMenuIcon("Icons.Explore");
|
||||
explore.IsEnabled = Directory.Exists(fullPath);
|
||||
explore.Click += (_, ev) =>
|
||||
{
|
||||
Native.OS.OpenInFileManager(fullPath, true);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var history = new MenuItem();
|
||||
history.Header = App.Text("DirHistories");
|
||||
history.Icon = App.CreateMenuIcon("Icons.Histories");
|
||||
history.Click += (_, ev) =>
|
||||
{
|
||||
App.ShowWindow(new ViewModels.DirHistories(repo, node.FullPath, commit.SHA));
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var patch = new MenuItem();
|
||||
patch.Header = App.Text("FileCM.SaveAsPatch");
|
||||
patch.Icon = App.CreateMenuIcon("Icons.Diff");
|
||||
patch.Click += async (_, e) =>
|
||||
{
|
||||
var storageProvider = TopLevel.GetTopLevel(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"] }];
|
||||
|
||||
var storageFile = await storageProvider.SaveFilePickerAsync(options);
|
||||
if (storageFile != null)
|
||||
{
|
||||
var saveTo = storageFile.Path.LocalPath;
|
||||
await vm.SaveChangesAsPatchAsync(changes, saveTo);
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
var copyPath = new MenuItem();
|
||||
copyPath.Header = App.Text("CopyPath");
|
||||
copyPath.Icon = App.CreateMenuIcon("Icons.Copy");
|
||||
copyPath.Tag = OperatingSystem.IsMacOS() ? "⌘+C" : "Ctrl+C";
|
||||
copyPath.Click += async (_, ev) =>
|
||||
{
|
||||
await App.CopyTextAsync(node.FullPath);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var copyFullPath = new MenuItem();
|
||||
copyFullPath.Header = App.Text("CopyFullPath");
|
||||
copyFullPath.Icon = App.CreateMenuIcon("Icons.Copy");
|
||||
copyFullPath.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+C" : "Ctrl+Shift+C";
|
||||
copyFullPath.Click += async (_, e) =>
|
||||
{
|
||||
await App.CopyTextAsync(fullPath);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
var menu = new ContextMenu();
|
||||
menu.Items.Add(explore);
|
||||
menu.Items.Add(new MenuItem { Header = "-" });
|
||||
menu.Items.Add(history);
|
||||
menu.Items.Add(patch);
|
||||
menu.Items.Add(new MenuItem { Header = "-" });
|
||||
menu.Items.Add(copyPath);
|
||||
menu.Items.Add(copyFullPath);
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
public ContextMenu CreateChangeContextMenu(Models.Change change)
|
||||
{
|
||||
if (DataContext is not ViewModels.CommitDetail { Repository: ViewModels.Repository repo, Commit: Models.Commit commit } vm)
|
||||
return null;
|
||||
|
||||
var openWithMerger = new MenuItem();
|
||||
openWithMerger.Header = App.Text("OpenInExternalMergeTool");
|
||||
openWithMerger.Icon = App.CreateMenuIcon("Icons.OpenWith");
|
||||
openWithMerger.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+D" : "Ctrl+Shift+D";
|
||||
openWithMerger.Click += (_, ev) =>
|
||||
{
|
||||
vm.OpenChangeInMergeTool(change);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var fullPath = Native.OS.GetAbsPath(repo.FullPath, change.Path);
|
||||
var explore = new MenuItem();
|
||||
explore.Header = App.Text("RevealFile");
|
||||
explore.Icon = App.CreateMenuIcon("Icons.Explore");
|
||||
explore.IsEnabled = File.Exists(fullPath);
|
||||
explore.Click += (_, ev) =>
|
||||
{
|
||||
Native.OS.OpenInFileManager(fullPath, true);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var history = new MenuItem();
|
||||
history.Header = App.Text("FileHistory");
|
||||
history.Icon = App.CreateMenuIcon("Icons.Histories");
|
||||
history.Click += (_, ev) =>
|
||||
{
|
||||
App.ShowWindow(new ViewModels.FileHistories(repo, change.Path, commit.SHA));
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var blame = new MenuItem();
|
||||
blame.Header = App.Text("Blame");
|
||||
blame.Icon = App.CreateMenuIcon("Icons.Blame");
|
||||
blame.IsEnabled = change.Index != Models.ChangeState.Deleted;
|
||||
blame.Click += (_, ev) =>
|
||||
{
|
||||
App.ShowWindow(new ViewModels.Blame(repo.FullPath, change.Path, commit));
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var patch = new MenuItem();
|
||||
patch.Header = App.Text("FileCM.SaveAsPatch");
|
||||
patch.Icon = App.CreateMenuIcon("Icons.Diff");
|
||||
patch.Click += async (_, e) =>
|
||||
{
|
||||
var storageProvider = TopLevel.GetTopLevel(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"] }];
|
||||
|
||||
var storageFile = await storageProvider.SaveFilePickerAsync(options);
|
||||
if (storageFile != null)
|
||||
{
|
||||
var saveTo = storageFile.Path.LocalPath;
|
||||
await vm.SaveChangesAsPatchAsync([change], saveTo);
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
var menu = new ContextMenu();
|
||||
menu.Items.Add(openWithMerger);
|
||||
menu.Items.Add(explore);
|
||||
menu.Items.Add(new MenuItem { Header = "-" });
|
||||
menu.Items.Add(history);
|
||||
menu.Items.Add(blame);
|
||||
menu.Items.Add(patch);
|
||||
menu.Items.Add(new MenuItem { Header = "-" });
|
||||
|
||||
if (!repo.IsBare)
|
||||
{
|
||||
var resetToThisRevision = new MenuItem();
|
||||
resetToThisRevision.Header = App.Text("ChangeCM.CheckoutThisRevision");
|
||||
resetToThisRevision.Icon = App.CreateMenuIcon("Icons.File.Checkout");
|
||||
resetToThisRevision.Click += async (_, ev) =>
|
||||
{
|
||||
await vm.ResetToThisRevisionAsync(change.Path);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var resetToFirstParent = new MenuItem();
|
||||
resetToFirstParent.Header = App.Text("ChangeCM.CheckoutFirstParentRevision");
|
||||
resetToFirstParent.Icon = App.CreateMenuIcon("Icons.File.Checkout");
|
||||
resetToFirstParent.IsEnabled = commit.Parents.Count > 0;
|
||||
resetToFirstParent.Click += async (_, ev) =>
|
||||
{
|
||||
await vm.ResetToParentRevisionAsync(change);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
menu.Items.Add(resetToThisRevision);
|
||||
menu.Items.Add(resetToFirstParent);
|
||||
menu.Items.Add(new MenuItem { Header = "-" });
|
||||
|
||||
if (repo.Remotes.Count > 0 && File.Exists(fullPath) && repo.IsLFSEnabled())
|
||||
{
|
||||
var lfs = new MenuItem();
|
||||
lfs.Header = App.Text("GitLFS");
|
||||
lfs.Icon = App.CreateMenuIcon("Icons.LFS");
|
||||
|
||||
var lfsLock = new MenuItem();
|
||||
lfsLock.Header = App.Text("GitLFS.Locks.Lock");
|
||||
lfsLock.Icon = App.CreateMenuIcon("Icons.Lock");
|
||||
if (repo.Remotes.Count == 1)
|
||||
{
|
||||
lfsLock.Click += async (_, e) =>
|
||||
{
|
||||
await repo.LockLFSFileAsync(repo.Remotes[0].Name, change.Path);
|
||||
e.Handled = true;
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var remote in repo.Remotes)
|
||||
{
|
||||
var remoteName = remote.Name;
|
||||
var lockRemote = new MenuItem();
|
||||
lockRemote.Header = remoteName;
|
||||
lockRemote.Click += async (_, e) =>
|
||||
{
|
||||
await repo.LockLFSFileAsync(remoteName, change.Path);
|
||||
e.Handled = true;
|
||||
};
|
||||
lfsLock.Items.Add(lockRemote);
|
||||
}
|
||||
}
|
||||
lfs.Items.Add(lfsLock);
|
||||
|
||||
var lfsUnlock = new MenuItem();
|
||||
lfsUnlock.Header = App.Text("GitLFS.Locks.Unlock");
|
||||
lfsUnlock.Icon = App.CreateMenuIcon("Icons.Unlock");
|
||||
if (repo.Remotes.Count == 1)
|
||||
{
|
||||
lfsUnlock.Click += async (_, e) =>
|
||||
{
|
||||
await repo.UnlockLFSFileAsync(repo.Remotes[0].Name, change.Path, false, true);
|
||||
e.Handled = true;
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var remote in repo.Remotes)
|
||||
{
|
||||
var remoteName = remote.Name;
|
||||
var unlockRemote = new MenuItem();
|
||||
unlockRemote.Header = remoteName;
|
||||
unlockRemote.Click += async (_, e) =>
|
||||
{
|
||||
await repo.UnlockLFSFileAsync(remoteName, change.Path, false, true);
|
||||
e.Handled = true;
|
||||
};
|
||||
lfsUnlock.Items.Add(unlockRemote);
|
||||
}
|
||||
}
|
||||
lfs.Items.Add(lfsUnlock);
|
||||
|
||||
menu.Items.Add(lfs);
|
||||
menu.Items.Add(new MenuItem() { Header = "-" });
|
||||
}
|
||||
}
|
||||
|
||||
var copyPath = new MenuItem();
|
||||
copyPath.Header = App.Text("CopyPath");
|
||||
copyPath.Icon = App.CreateMenuIcon("Icons.Copy");
|
||||
copyPath.Tag = OperatingSystem.IsMacOS() ? "⌘+C" : "Ctrl+C";
|
||||
copyPath.Click += async (_, ev) =>
|
||||
{
|
||||
await App.CopyTextAsync(change.Path);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var copyFullPath = new MenuItem();
|
||||
copyFullPath.Header = App.Text("CopyFullPath");
|
||||
copyFullPath.Icon = App.CreateMenuIcon("Icons.Copy");
|
||||
copyFullPath.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+C" : "Ctrl+Shift+C";
|
||||
copyFullPath.Click += async (_, e) =>
|
||||
{
|
||||
await App.CopyTextAsync(fullPath);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
menu.Items.Add(copyPath);
|
||||
menu.Items.Add(copyFullPath);
|
||||
return menu;
|
||||
}
|
||||
|
||||
private async void OnCommitListKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (DataContext is ViewModels.CommitDetail detail &&
|
||||
@@ -48,11 +328,7 @@ namespace SourceGit.Views
|
||||
private void OnChangeContextRequested(object sender, ContextRequestedEventArgs e)
|
||||
{
|
||||
if (DataContext is ViewModels.CommitDetail detail && sender is Grid { DataContext: Models.Change change } grid)
|
||||
{
|
||||
var menu = detail.CreateChangeContextMenu(change);
|
||||
menu?.Open(grid);
|
||||
}
|
||||
|
||||
CreateChangeContextMenu(change)?.Open(grid);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
using Avalonia.Collections;
|
||||
@@ -8,6 +9,7 @@ using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.VisualTree;
|
||||
|
||||
namespace SourceGit.Views
|
||||
@@ -139,7 +141,27 @@ namespace SourceGit.Views
|
||||
var detailView = this.FindAncestorOfType<CommitDetail>();
|
||||
if (detailView is { DataContext: ViewModels.CommitDetail detail })
|
||||
{
|
||||
await detail.SaveRevisionFile(file);
|
||||
var storageProvider = TopLevel.GetTopLevel(this).StorageProvider;
|
||||
if (storageProvider == null)
|
||||
return;
|
||||
|
||||
var options = new FolderPickerOpenOptions() { AllowMultiple = false };
|
||||
try
|
||||
{
|
||||
var selected = await 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 saveTo = Path.Combine(folderPath, Path.GetFileName(file.Path)!);
|
||||
await detail.SaveRevisionFileAsync(file, saveTo);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
App.RaiseException(detail.Repository.FullPath, $"Failed to save file: {ex.Message}");
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
@@ -308,10 +330,14 @@ namespace SourceGit.Views
|
||||
|
||||
private void OnTreeNodeContextRequested(object sender, ContextRequestedEventArgs e)
|
||||
{
|
||||
if (DataContext is ViewModels.CommitDetail vm &&
|
||||
if (DataContext is ViewModels.CommitDetail { Repository: ViewModels.Repository repo, Commit: Models.Commit commit } vm &&
|
||||
sender is Grid { DataContext: ViewModels.RevisionFileTreeNode { Backend: { } obj } } grid)
|
||||
{
|
||||
var menu = vm.CreateRevisionFileContextMenu(obj);
|
||||
var menu = obj.Type switch
|
||||
{
|
||||
Models.ObjectType.Tree => CreateRevisionFileContextMenuByFolder(repo, vm, commit, obj.Path),
|
||||
_ => CreateRevisionFileContextMenu(repo, vm, commit, obj),
|
||||
};
|
||||
menu.Open(grid);
|
||||
}
|
||||
|
||||
@@ -387,6 +413,261 @@ namespace SourceGit.Views
|
||||
});
|
||||
}
|
||||
|
||||
public ContextMenu CreateRevisionFileContextMenuByFolder(ViewModels.Repository repo, ViewModels.CommitDetail vm, Models.Commit commit, string path)
|
||||
{
|
||||
var fullPath = Native.OS.GetAbsPath(repo.FullPath, path);
|
||||
var explore = new MenuItem();
|
||||
explore.Header = App.Text("RevealFile");
|
||||
explore.Icon = App.CreateMenuIcon("Icons.Explore");
|
||||
explore.IsEnabled = Directory.Exists(fullPath);
|
||||
explore.Click += (_, ev) =>
|
||||
{
|
||||
Native.OS.OpenInFileManager(fullPath, true);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var history = new MenuItem();
|
||||
history.Header = App.Text("DirHistories");
|
||||
history.Icon = App.CreateMenuIcon("Icons.Histories");
|
||||
history.Click += (_, ev) =>
|
||||
{
|
||||
App.ShowWindow(new ViewModels.DirHistories(repo, path, commit.SHA));
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var copyPath = new MenuItem();
|
||||
copyPath.Header = App.Text("CopyPath");
|
||||
copyPath.Icon = App.CreateMenuIcon("Icons.Copy");
|
||||
copyPath.Tag = OperatingSystem.IsMacOS() ? "⌘+C" : "Ctrl+C";
|
||||
copyPath.Click += async (_, ev) =>
|
||||
{
|
||||
await App.CopyTextAsync(path);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var copyFullPath = new MenuItem();
|
||||
copyFullPath.Header = App.Text("CopyFullPath");
|
||||
copyFullPath.Icon = App.CreateMenuIcon("Icons.Copy");
|
||||
copyFullPath.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+C" : "Ctrl+Shift+C";
|
||||
copyFullPath.Click += async (_, e) =>
|
||||
{
|
||||
await App.CopyTextAsync(fullPath);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
var menu = new ContextMenu();
|
||||
menu.Items.Add(explore);
|
||||
menu.Items.Add(new MenuItem() { Header = "-" });
|
||||
menu.Items.Add(history);
|
||||
menu.Items.Add(new MenuItem() { Header = "-" });
|
||||
menu.Items.Add(copyPath);
|
||||
menu.Items.Add(copyFullPath);
|
||||
return menu;
|
||||
}
|
||||
|
||||
public ContextMenu CreateRevisionFileContextMenu(ViewModels.Repository repo, ViewModels.CommitDetail vm, Models.Commit commit, Models.Object file)
|
||||
{
|
||||
var fullPath = Native.OS.GetAbsPath(repo.FullPath, file.Path);
|
||||
var menu = new ContextMenu();
|
||||
|
||||
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.Type == Models.ObjectType.Blob;
|
||||
openWith.Click += async (_, ev) =>
|
||||
{
|
||||
await vm.OpenRevisionFileWithDefaultEditorAsync(file.Path);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var saveAs = new MenuItem();
|
||||
saveAs.Header = App.Text("SaveAs");
|
||||
saveAs.Icon = App.CreateMenuIcon("Icons.Save");
|
||||
saveAs.IsEnabled = file.Type == Models.ObjectType.Blob;
|
||||
saveAs.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+S" : "Ctrl+Shift+S";
|
||||
saveAs.Click += async (_, ev) =>
|
||||
{
|
||||
var storageProvider = TopLevel.GetTopLevel(this).StorageProvider;
|
||||
if (storageProvider == null)
|
||||
return;
|
||||
|
||||
var options = new FolderPickerOpenOptions() { AllowMultiple = false };
|
||||
try
|
||||
{
|
||||
var selected = await 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 saveTo = Path.Combine(folderPath, Path.GetFileName(file.Path)!);
|
||||
await vm.SaveRevisionFileAsync(file, saveTo);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
App.RaiseException(repo.FullPath, $"Failed to save file: {e.Message}");
|
||||
}
|
||||
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var explore = new MenuItem();
|
||||
explore.Header = App.Text("RevealFile");
|
||||
explore.Icon = App.CreateMenuIcon("Icons.Explore");
|
||||
explore.IsEnabled = File.Exists(fullPath);
|
||||
explore.Click += (_, ev) =>
|
||||
{
|
||||
Native.OS.OpenInFileManager(fullPath, file.Type == Models.ObjectType.Blob);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
menu.Items.Add(openWith);
|
||||
menu.Items.Add(saveAs);
|
||||
menu.Items.Add(explore);
|
||||
menu.Items.Add(new MenuItem() { Header = "-" });
|
||||
|
||||
var history = new MenuItem();
|
||||
history.Header = App.Text("FileHistory");
|
||||
history.Icon = App.CreateMenuIcon("Icons.Histories");
|
||||
history.Click += (_, ev) =>
|
||||
{
|
||||
App.ShowWindow(new ViewModels.FileHistories(repo, file.Path, commit.SHA));
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var blame = new MenuItem();
|
||||
blame.Header = App.Text("Blame");
|
||||
blame.Icon = App.CreateMenuIcon("Icons.Blame");
|
||||
blame.IsEnabled = file.Type == Models.ObjectType.Blob;
|
||||
blame.Click += (_, ev) =>
|
||||
{
|
||||
App.ShowWindow(new ViewModels.Blame(repo.FullPath, file.Path, commit));
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
menu.Items.Add(history);
|
||||
menu.Items.Add(blame);
|
||||
menu.Items.Add(new MenuItem() { Header = "-" });
|
||||
|
||||
if (!repo.IsBare)
|
||||
{
|
||||
var resetToThisRevision = new MenuItem();
|
||||
resetToThisRevision.Header = App.Text("ChangeCM.CheckoutThisRevision");
|
||||
resetToThisRevision.Icon = App.CreateMenuIcon("Icons.File.Checkout");
|
||||
resetToThisRevision.Click += async (_, ev) =>
|
||||
{
|
||||
await vm.ResetToThisRevisionAsync(file.Path);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var change = vm.Changes.Find(x => x.Path == file.Path) ?? new Models.Change() { Index = Models.ChangeState.None, Path = file.Path };
|
||||
var resetToFirstParent = new MenuItem();
|
||||
resetToFirstParent.Header = App.Text("ChangeCM.CheckoutFirstParentRevision");
|
||||
resetToFirstParent.Icon = App.CreateMenuIcon("Icons.File.Checkout");
|
||||
resetToFirstParent.IsEnabled = commit.Parents.Count > 0;
|
||||
resetToFirstParent.Click += async (_, ev) =>
|
||||
{
|
||||
await vm.ResetToParentRevisionAsync(change);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
menu.Items.Add(resetToThisRevision);
|
||||
menu.Items.Add(resetToFirstParent);
|
||||
menu.Items.Add(new MenuItem() { Header = "-" });
|
||||
|
||||
if (repo.Remotes.Count > 0 && File.Exists(fullPath) && repo.IsLFSEnabled())
|
||||
{
|
||||
var lfs = new MenuItem();
|
||||
lfs.Header = App.Text("GitLFS");
|
||||
lfs.Icon = App.CreateMenuIcon("Icons.LFS");
|
||||
|
||||
var lfsLock = new MenuItem();
|
||||
lfsLock.Header = App.Text("GitLFS.Locks.Lock");
|
||||
lfsLock.Icon = App.CreateMenuIcon("Icons.Lock");
|
||||
if (repo.Remotes.Count == 1)
|
||||
{
|
||||
lfsLock.Click += async (_, e) =>
|
||||
{
|
||||
await repo.LockLFSFileAsync(repo.Remotes[0].Name, change.Path);
|
||||
e.Handled = true;
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var remote in repo.Remotes)
|
||||
{
|
||||
var remoteName = remote.Name;
|
||||
var lockRemote = new MenuItem();
|
||||
lockRemote.Header = remoteName;
|
||||
lockRemote.Click += async (_, e) =>
|
||||
{
|
||||
await repo.LockLFSFileAsync(remoteName, change.Path);
|
||||
e.Handled = true;
|
||||
};
|
||||
lfsLock.Items.Add(lockRemote);
|
||||
}
|
||||
}
|
||||
lfs.Items.Add(lfsLock);
|
||||
|
||||
var lfsUnlock = new MenuItem();
|
||||
lfsUnlock.Header = App.Text("GitLFS.Locks.Unlock");
|
||||
lfsUnlock.Icon = App.CreateMenuIcon("Icons.Unlock");
|
||||
if (repo.Remotes.Count == 1)
|
||||
{
|
||||
lfsUnlock.Click += async (_, e) =>
|
||||
{
|
||||
await repo.UnlockLFSFileAsync(repo.Remotes[0].Name, change.Path, false, true);
|
||||
e.Handled = true;
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var remote in repo.Remotes)
|
||||
{
|
||||
var remoteName = remote.Name;
|
||||
var unlockRemote = new MenuItem();
|
||||
unlockRemote.Header = remoteName;
|
||||
unlockRemote.Click += async (_, e) =>
|
||||
{
|
||||
await repo.UnlockLFSFileAsync(remoteName, change.Path, false, true);
|
||||
e.Handled = true;
|
||||
};
|
||||
lfsUnlock.Items.Add(unlockRemote);
|
||||
}
|
||||
}
|
||||
lfs.Items.Add(lfsUnlock);
|
||||
|
||||
menu.Items.Add(lfs);
|
||||
menu.Items.Add(new MenuItem() { Header = "-" });
|
||||
}
|
||||
}
|
||||
|
||||
var copyPath = new MenuItem();
|
||||
copyPath.Header = App.Text("CopyPath");
|
||||
copyPath.Icon = App.CreateMenuIcon("Icons.Copy");
|
||||
copyPath.Tag = OperatingSystem.IsMacOS() ? "⌘+C" : "Ctrl+C";
|
||||
copyPath.Click += async (_, ev) =>
|
||||
{
|
||||
await App.CopyTextAsync(file.Path);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var copyFullPath = new MenuItem();
|
||||
copyFullPath.Header = App.Text("CopyFullPath");
|
||||
copyFullPath.Icon = App.CreateMenuIcon("Icons.Copy");
|
||||
copyFullPath.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+C" : "Ctrl+Shift+C";
|
||||
copyFullPath.Click += async (_, e) =>
|
||||
{
|
||||
await App.CopyTextAsync(fullPath);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
menu.Items.Add(copyPath);
|
||||
menu.Items.Add(copyFullPath);
|
||||
return menu;
|
||||
}
|
||||
|
||||
private List<ViewModels.RevisionFileTreeNode> _tree = [];
|
||||
private bool _disableSelectionChangingEvent = false;
|
||||
private List<ViewModels.RevisionFileTreeNode> _searchResult = [];
|
||||
|
||||
Reference in New Issue
Block a user