diff --git a/src/Commands/Command.cs b/src/Commands/Command.cs index adf87876..5275a479 100644 --- a/src/Commands/Command.cs +++ b/src/Commands/Command.cs @@ -7,9 +7,9 @@ using System.Text.RegularExpressions; namespace SourceGit.Commands { /// - /// 取消命令执行的对象 + /// 用于取消命令执行的上下文对象 /// - public class Cancellable { + public class Context { public bool IsCancelRequested { get; set; } = false; } @@ -27,6 +27,11 @@ namespace SourceGit.Commands { public string Error { get; set; } } + /// + /// 上下文 + /// + public Context Ctx { get; set; } = null; + /// /// 运行路径 /// @@ -42,11 +47,6 @@ namespace SourceGit.Commands { /// public bool TraitErrorAsOutput { get; set; } = false; - /// - /// 用于取消命令指行的Token - /// - public Cancellable Token { get; set; } = null; - /// /// 运行 /// @@ -69,7 +69,7 @@ namespace SourceGit.Commands { var isCancelled = false; proc.OutputDataReceived += (o, e) => { - if (Token != null && Token.IsCancelRequested) { + if (Ctx != null && Ctx.IsCancelRequested) { isCancelled = true; proc.CancelErrorRead(); proc.CancelOutputRead(); @@ -85,7 +85,7 @@ namespace SourceGit.Commands { OnReadline(e.Data); }; proc.ErrorDataReceived += (o, e) => { - if (Token != null && Token.IsCancelRequested) { + if (Ctx != null && Ctx.IsCancelRequested) { isCancelled = true; proc.CancelErrorRead(); proc.CancelOutputRead(); diff --git a/src/Views/Widgets/CommitChanges.xaml.cs b/src/Views/Widgets/CommitChanges.xaml.cs index 1fb82de1..baca6f3c 100644 --- a/src/Views/Widgets/CommitChanges.xaml.cs +++ b/src/Views/Widgets/CommitChanges.xaml.cs @@ -211,21 +211,9 @@ namespace SourceGit.Views.Widgets { ev.Handled = true; }; - var saveAs = new MenuItem(); - saveAs.Header = App.Text("SaveAs"); - saveAs.Visibility = range.Count == 1 ? Visibility.Visible : Visibility.Collapsed; - saveAs.Click += (obj, ev) => { - FolderBrowser.Open(null, App.Text("SaveFileTo"), saveTo => { - var full = Path.Combine(saveTo, Path.GetFileName(path)); - new Commands.SaveRevisionFile(repo, path, range[0].SHA, full).Exec(); - }); - ev.Handled = true; - }; - menu.Items.Add(history); menu.Items.Add(blame); menu.Items.Add(explore); - menu.Items.Add(saveAs); } var copyPath = new MenuItem(); diff --git a/src/Views/Widgets/CommitDetail.xaml b/src/Views/Widgets/CommitDetail.xaml index 8a23ace6..e8279fe8 100644 --- a/src/Views/Widgets/CommitDetail.xaml +++ b/src/Views/Widgets/CommitDetail.xaml @@ -4,18 +4,10 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:controls="clr-namespace:SourceGit.Views.Controls" - xmlns:converters="clr-namespace:SourceGit.Views.Converters" xmlns:models="clr-namespace:SourceGit.Models" xmlns:widgets="clr-namespace:SourceGit.Views.Widgets" mc:Ignorable="d" - d:DesignHeight="450" d:DesignWidth="800"> - - - - - + d:DesignHeight="450" d:DesignWidth="800"> @@ -209,7 +201,7 @@ RowHeight="24" Margin="11,0,0,2"> - @@ -241,104 +233,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/src/Views/Widgets/CommitDetail.xaml.cs b/src/Views/Widgets/CommitDetail.xaml.cs index ef3a8d6e..cbce62e8 100644 --- a/src/Views/Widgets/CommitDetail.xaml.cs +++ b/src/Views/Widgets/CommitDetail.xaml.cs @@ -1,14 +1,9 @@ -using System; using System.Collections.Generic; using System.Diagnostics; -using System.Globalization; using System.IO; -using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; -using System.Windows.Media; -using System.Windows.Media.Imaging; using System.Windows.Navigation; namespace SourceGit.Views.Widgets { @@ -19,18 +14,7 @@ namespace SourceGit.Views.Widgets { public partial class CommitDetail : UserControl { private string repo = null; private Models.Commit commit = null; - private Commands.Cancellable cancelToken = new Commands.Cancellable(); - - /// - /// 文件列表树节点 - /// - public class FileNode { - public Models.ObjectType Type { get; set; } = Models.ObjectType.None; - public string Path { get; set; } = ""; - public string SHA { get; set; } = null; - public bool IsFolder => Type == Models.ObjectType.None; - public List Children { get; set; } = new List(); - } + private Commands.Context cancelToken = new Commands.Context(); public CommitDetail() { InitializeComponent(); @@ -38,14 +22,14 @@ namespace SourceGit.Views.Widgets { public void SetData(string repo, Models.Commit commit) { cancelToken.IsCancelRequested = true; - cancelToken = new Commands.Cancellable(); + cancelToken = new Commands.Context(); this.repo = repo; this.commit = commit; + revisionFiles.SetData(repo, commit.SHA, cancelToken); UpdateInformation(commit); UpdateChanges(); - UpdateRevisionFiles(); } #region DATA @@ -91,10 +75,10 @@ namespace SourceGit.Views.Widgets { } private void UpdateChanges() { - var cmd = new Commands.CommitChanges(repo, commit.SHA) { Token = cancelToken }; + var cmd = new Commands.CommitChanges(repo, commit.SHA) { Ctx = cancelToken }; Task.Run(() => { var changes = cmd.Result(); - if (cmd.Token.IsCancelRequested) return; + if (cmd.Ctx.IsCancelRequested) return; Dispatcher.Invoke(() => { changeList.ItemsSource = changes; @@ -102,90 +86,6 @@ namespace SourceGit.Views.Widgets { }); }); } - - private void SortFileNodes(List nodes) { - nodes.Sort((l, r) => { - if (l.IsFolder == r.IsFolder) { - return l.Path.CompareTo(r.Path); - } else { - return l.IsFolder ? -1 : 1; - } - }); - - foreach (var node in nodes) { - if (node.Children.Count > 1) SortFileNodes(node.Children); - } - } - - private void UpdateRevisionFiles() { - var cmd = new Commands.RevisionObjects(repo, commit.SHA) { Token = cancelToken }; - Task.Run(() => { - var objects = cmd.Result(); - if (cmd.Token.IsCancelRequested) return; - - var nodes = new List(); - var folders = new Dictionary(); - - foreach (var obj in objects) { - var sepIdx = obj.Path.IndexOf('/'); - if (sepIdx == -1) { - nodes.Add(new FileNode() { - Type = obj.Type, - Path = obj.Path, - SHA = obj.SHA, - }); - } else { - FileNode lastFolder = null; - var start = 0; - - while (sepIdx != -1) { - var folder = obj.Path.Substring(0, sepIdx); - if (folders.ContainsKey(folder)) { - lastFolder = folders[folder]; - } else if (lastFolder == null) { - lastFolder = new FileNode() { - Type = Models.ObjectType.None, - Path = folder, - SHA = null, - }; - nodes.Add(lastFolder); - folders.Add(folder, lastFolder); - } else { - var cur = new FileNode() { - Type = Models.ObjectType.None, - Path = folder, - SHA = null, - }; - folders.Add(folder, cur); - lastFolder.Children.Add(cur); - lastFolder = cur; - } - - start = sepIdx + 1; - sepIdx = obj.Path.IndexOf('/', start); - } - - lastFolder.Children.Add(new FileNode() { - Type = obj.Type, - Path = obj.Path, - SHA = obj.SHA, - }); - } - - obj.Path = null; - } - - folders.Clear(); - objects.Clear(); - - SortFileNodes(nodes); - - Dispatcher.Invoke(() => { - treeFiles.ItemsSource = nodes; - GC.Collect(); - }); - }); - } #endregion #region INFORMATION @@ -201,214 +101,48 @@ namespace SourceGit.Views.Widgets { if (change == null) return; var menu = new ContextMenu(); - FillContextMenu(menu, change.Path, change.Index == Models.Change.Status.Deleted, true); - menu.IsOpen = true; - e.Handled = true; - } - #endregion - - #region REVISION_FILES - private bool IsImageFile(string path) { - return path.EndsWith(".png") || - path.EndsWith(".jpg") || - path.EndsWith(".jpeg") || - path.EndsWith(".ico") || - path.EndsWith(".bmp") || - path.EndsWith(".tiff") || - path.EndsWith(".gif"); - } - - private void LayoutTextPreview(List lines) { - var font = new FontFamily("Consolas"); - - var maxLineNumber = $"{lines.Count + 1}"; - var formatted = new FormattedText( - maxLineNumber, - CultureInfo.CurrentCulture, - FlowDirection.LeftToRight, - new Typeface(font, FontStyles.Normal, FontWeights.Normal, FontStretches.Normal), - 12.0, - Brushes.Black, - VisualTreeHelper.GetDpi(this).PixelsPerDip); - - var offset = formatted.Width + 16; - if (lines.Count * 16 > layerTextPreview.ActualHeight) offset += 8; - - txtPreviewData.ItemsSource = lines; - txtPreviewData.Columns[0].Width = new DataGridLength(formatted.Width + 16, DataGridLengthUnitType.Pixel); - txtPreviewData.Columns[1].Width = DataGridLength.Auto; - txtPreviewData.Columns[1].Width = DataGridLength.SizeToCells; - txtPreviewData.Columns[1].MinWidth = layerTextPreview.ActualWidth - offset; - - txtPreviewSplitter.Margin = new Thickness(formatted.Width + 15, 0, 0, 0); - } - - private void OnTextPreviewSizeChanged(object sender, SizeChangedEventArgs e) { - if (txtPreviewData == null) return; - - var offset = txtPreviewData.NonFrozenColumnsViewportHorizontalOffset; - if (txtPreviewData.Items.Count * 16 > layerTextPreview.ActualHeight) offset += 8; - - txtPreviewData.Columns[1].Width = DataGridLength.Auto; - txtPreviewData.Columns[1].Width = DataGridLength.SizeToCells; - txtPreviewData.Columns[1].MinWidth = layerTextPreview.ActualWidth - offset; - txtPreviewData.UpdateLayout(); - } - - private void OnTextPreviewContextMenuOpening(object sender, ContextMenuEventArgs e) { - var grid = sender as DataGrid; - if (grid == null) return; - - var menu = new ContextMenu(); - - var copyIcon = new System.Windows.Shapes.Path(); - copyIcon.Data = FindResource("Icon.Copy") as Geometry; - copyIcon.Width = 10; - - var copy = new MenuItem(); - copy.Header = "Copy"; - copy.Icon = copyIcon; - copy.Click += (o, ev) => { - var items = grid.SelectedItems; - if (items.Count == 0) return; - - var builder = new StringBuilder(); - foreach (var item in items) { - var line = item as Models.TextLine; - if (line == null) continue; - - builder.Append(line.Data); - builder.AppendLine(); - } - - Clipboard.SetText(builder.ToString()); - }; - menu.Items.Add(copy); - menu.IsOpen = true; - e.Handled = true; - } - - private void OnFilesSelectionChanged(object sender, RoutedEventArgs e) { - layerTextPreview.Visibility = Visibility.Collapsed; - layerImagePreview.Visibility = Visibility.Collapsed; - layerRevisionPreview.Visibility = Visibility.Collapsed; - layerBinaryPreview.Visibility = Visibility.Collapsed; - txtPreviewData.ItemsSource = null; - - if (treeFiles.Selected.Count == 0) return; - - var node = treeFiles.Selected[0] as FileNode; - switch (node.Type) { - case Models.ObjectType.Blob: - if (IsImageFile(node.Path)) { - var tmp = Path.GetTempFileName(); - new Commands.SaveRevisionFile(repo, node.Path, commit.SHA, tmp).Exec(); - - layerImagePreview.Visibility = Visibility.Visible; - imgPreviewData.Source = new BitmapImage(new Uri(tmp, UriKind.Absolute)); - } else if (new Commands.IsLFSFiltered(repo, node.Path).Result()) { - var lfs = new Commands.QueryLFSObject(repo, commit.SHA, node.Path).Result(); - layerRevisionPreview.Visibility = Visibility.Visible; - iconRevisionPreview.Data = FindResource("Icon.LFS") as Geometry; - txtRevisionPreview.Text = "LFS SIZE: " + App.Text("Bytes", lfs.Size); - } else if (new Commands.IsBinaryFile(repo, commit.SHA, node.Path).Result()) { - layerBinaryPreview.Visibility = Visibility.Visible; - } else { - layerTextPreview.Visibility = Visibility.Visible; - Task.Run(() => { - var lines = new Commands.QueryFileContent(repo, commit.SHA, node.Path).Result(); - Dispatcher.Invoke(() => LayoutTextPreview(lines)); - }); - } - break; - case Models.ObjectType.Tag: - layerRevisionPreview.Visibility = Visibility.Visible; - iconRevisionPreview.Data = FindResource("Icon.Tag") as Geometry; - txtRevisionPreview.Text = "TAG: " + node.SHA; - break; - case Models.ObjectType.Commit: - layerRevisionPreview.Visibility = Visibility.Visible; - iconRevisionPreview.Data = FindResource("Icon.Submodule") as Geometry; - txtRevisionPreview.Text = "SUBMODULE: " + node.SHA; - break; - case Models.ObjectType.Tree: - layerRevisionPreview.Visibility = Visibility.Visible; - iconRevisionPreview.Data = FindResource("Icon.Tree") as Geometry; - txtRevisionPreview.Text = "TREE: " + node.SHA; - break; - default: - return; - } - } - - private void OnFilesContextMenuOpening(object sender, ContextMenuEventArgs e) { - var item = treeFiles.FindItem(e.OriginalSource as DependencyObject); - if (item == null) return; - - var node = item.DataContext as FileNode; - if (node == null || node.IsFolder) return; - - var menu = new ContextMenu(); - FillContextMenu(menu, node.Path, false, node.Type == Models.ObjectType.Blob); - menu.IsOpen = true; - e.Handled = true; - } - #endregion - - #region COMMON - private void FillContextMenu(ContextMenu menu, string path, bool isDeleted, bool canSave) { - if (!isDeleted) { + if (change.Index != Models.Change.Status.Deleted) { var history = new MenuItem(); history.Header = App.Text("FileHistory"); + history.IsEnabled = change.Index != Models.Change.Status.Deleted; history.Click += (o, ev) => { - var viewer = new Views.Histories(repo, path); + var viewer = new Views.Histories(repo, change.Path); viewer.Show(); ev.Handled = true; }; var blame = new MenuItem(); blame.Header = App.Text("Blame"); + blame.IsEnabled = change.Index != Models.Change.Status.Deleted; blame.Click += (obj, ev) => { - var viewer = new Blame(repo, path, commit.SHA); + var viewer = new Blame(repo, change.Path, commit.SHA); viewer.Show(); ev.Handled = true; }; var explore = new MenuItem(); explore.Header = App.Text("RevealFile"); + explore.IsEnabled = change.Index != Models.Change.Status.Deleted; explore.Click += (o, ev) => { - var full = Path.GetFullPath(repo + "\\" + path); + var full = Path.GetFullPath(repo + "\\" + change.Path); Process.Start("explorer", $"/select,{full}"); ev.Handled = true; }; - var saveAs = new MenuItem(); - saveAs.Header = App.Text("SaveAs"); - saveAs.IsEnabled = canSave; - saveAs.Click += (obj, ev) => { - FolderBrowser.Open(null, App.Text("SaveFileTo"), saveTo => { - var full = Path.Combine(saveTo, Path.GetFileName(path)); - new Commands.SaveRevisionFile(repo, path, commit.SHA, full).Exec(); - }); - ev.Handled = true; - }; - menu.Items.Add(history); menu.Items.Add(blame); menu.Items.Add(explore); - menu.Items.Add(saveAs); } var copyPath = new MenuItem(); copyPath.Header = App.Text("CopyPath"); copyPath.Click += (obj, ev) => { - Clipboard.SetText(path); + Clipboard.SetText(change.Path); + ev.Handled = true; }; - menu.Items.Add(copyPath); - } - private void OnRequestBringIntoView(object sender, RequestBringIntoViewEventArgs e) { + menu.IsOpen = true; e.Handled = true; } #endregion diff --git a/src/Views/Widgets/RevisionFiles.xaml b/src/Views/Widgets/RevisionFiles.xaml new file mode 100644 index 00000000..90817581 --- /dev/null +++ b/src/Views/Widgets/RevisionFiles.xaml @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Views/Widgets/RevisionFiles.xaml.cs b/src/Views/Widgets/RevisionFiles.xaml.cs new file mode 100644 index 00000000..ae3c08a6 --- /dev/null +++ b/src/Views/Widgets/RevisionFiles.xaml.cs @@ -0,0 +1,322 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace SourceGit.Views.Widgets { + /// + /// 提交信息面板中的文件列表分页 + /// + public partial class RevisionFiles : UserControl { + private string repo = null; + private string sha = null; + + /// + /// 文件列表树节点 + /// + public class FileNode { + public Models.ObjectType Type { get; set; } = Models.ObjectType.None; + public string Path { get; set; } = ""; + public string SHA { get; set; } = null; + public bool IsFolder => Type == Models.ObjectType.None; + public List Children { get; set; } = new List(); + } + + public RevisionFiles() { + InitializeComponent(); + } + + public void SetData(string repo, string sha, Commands.Context cancelToken) { + this.repo = repo; + this.sha = sha; + + var cmd = new Commands.RevisionObjects(repo, sha) { Ctx = cancelToken }; + Task.Run(() => { + var objects = cmd.Result(); + if (cmd.Ctx.IsCancelRequested) return; + + var nodes = new List(); + var folders = new Dictionary(); + + foreach (var obj in objects) { + var sepIdx = obj.Path.IndexOf('/'); + if (sepIdx == -1) { + nodes.Add(new FileNode() { + Type = obj.Type, + Path = obj.Path, + SHA = obj.SHA, + }); + } else { + FileNode lastFolder = null; + var start = 0; + + while (sepIdx != -1) { + var folder = obj.Path.Substring(0, sepIdx); + if (folders.ContainsKey(folder)) { + lastFolder = folders[folder]; + } else if (lastFolder == null) { + lastFolder = new FileNode() { + Type = Models.ObjectType.None, + Path = folder, + SHA = null, + }; + nodes.Add(lastFolder); + folders.Add(folder, lastFolder); + } else { + var cur = new FileNode() { + Type = Models.ObjectType.None, + Path = folder, + SHA = null, + }; + folders.Add(folder, cur); + lastFolder.Children.Add(cur); + lastFolder = cur; + } + + start = sepIdx + 1; + sepIdx = obj.Path.IndexOf('/', start); + } + + lastFolder.Children.Add(new FileNode() { + Type = obj.Type, + Path = obj.Path, + SHA = obj.SHA, + }); + } + + obj.Path = null; + } + + folders.Clear(); + objects.Clear(); + + SortFileNodes(nodes); + + Dispatcher.Invoke(() => { + treeFiles.ItemsSource = nodes; + GC.Collect(); + }); + }); + } + + private void SortFileNodes(List nodes) { + nodes.Sort((l, r) => { + if (l.IsFolder == r.IsFolder) { + return l.Path.CompareTo(r.Path); + } else { + return l.IsFolder ? -1 : 1; + } + }); + + foreach (var node in nodes) { + if (node.Children.Count > 1) SortFileNodes(node.Children); + } + } + + private bool IsImageFile(string path) { + return path.EndsWith(".png") || + path.EndsWith(".jpg") || + path.EndsWith(".jpeg") || + path.EndsWith(".ico") || + path.EndsWith(".bmp") || + path.EndsWith(".tiff") || + path.EndsWith(".gif"); + } + + #region EVENTS + private void LayoutTextPreview(List lines) { + var font = new FontFamily("Consolas"); + + var maxLineNumber = $"{lines.Count + 1}"; + var formatted = new FormattedText( + maxLineNumber, + CultureInfo.CurrentCulture, + FlowDirection.LeftToRight, + new Typeface(font, FontStyles.Normal, FontWeights.Normal, FontStretches.Normal), + 12.0, + Brushes.Black, + VisualTreeHelper.GetDpi(this).PixelsPerDip); + + var offset = formatted.Width + 16; + if (lines.Count * 16 > layerTextPreview.ActualHeight) offset += 8; + + txtPreviewData.ItemsSource = lines; + txtPreviewData.Columns[0].Width = new DataGridLength(formatted.Width + 16, DataGridLengthUnitType.Pixel); + txtPreviewData.Columns[1].Width = DataGridLength.Auto; + txtPreviewData.Columns[1].Width = DataGridLength.SizeToCells; + txtPreviewData.Columns[1].MinWidth = layerTextPreview.ActualWidth - offset; + + txtPreviewSplitter.Margin = new Thickness(formatted.Width + 15, 0, 0, 0); + } + + private void OnTextPreviewSizeChanged(object sender, SizeChangedEventArgs e) { + if (txtPreviewData == null) return; + + var offset = txtPreviewData.NonFrozenColumnsViewportHorizontalOffset; + if (txtPreviewData.Items.Count * 16 > layerTextPreview.ActualHeight) offset += 8; + + txtPreviewData.Columns[1].Width = DataGridLength.Auto; + txtPreviewData.Columns[1].Width = DataGridLength.SizeToCells; + txtPreviewData.Columns[1].MinWidth = layerTextPreview.ActualWidth - offset; + txtPreviewData.UpdateLayout(); + } + + private void OnTextPreviewContextMenuOpening(object sender, ContextMenuEventArgs e) { + var grid = sender as DataGrid; + if (grid == null) return; + + var menu = new ContextMenu(); + + var copyIcon = new System.Windows.Shapes.Path(); + copyIcon.Data = FindResource("Icon.Copy") as Geometry; + copyIcon.Width = 10; + + var copy = new MenuItem(); + copy.Header = "Copy"; + copy.Icon = copyIcon; + copy.Click += (o, ev) => { + var items = grid.SelectedItems; + if (items.Count == 0) return; + + var builder = new StringBuilder(); + foreach (var item in items) { + var line = item as Models.TextLine; + if (line == null) continue; + + builder.Append(line.Data); + builder.AppendLine(); + } + + Clipboard.SetText(builder.ToString()); + }; + menu.Items.Add(copy); + menu.IsOpen = true; + e.Handled = true; + } + + private void OnFilesSelectionChanged(object sender, RoutedEventArgs e) { + layerTextPreview.Visibility = Visibility.Collapsed; + layerImagePreview.Visibility = Visibility.Collapsed; + layerRevisionPreview.Visibility = Visibility.Collapsed; + layerBinaryPreview.Visibility = Visibility.Collapsed; + txtPreviewData.ItemsSource = null; + + if (treeFiles.Selected.Count == 0) return; + + var node = treeFiles.Selected[0] as FileNode; + switch (node.Type) { + case Models.ObjectType.Blob: + if (IsImageFile(node.Path)) { + var tmp = Path.GetTempFileName(); + new Commands.SaveRevisionFile(repo, node.Path, sha, tmp).Exec(); + + layerImagePreview.Visibility = Visibility.Visible; + imgPreviewData.Source = new BitmapImage(new Uri(tmp, UriKind.Absolute)); + } else if (new Commands.IsLFSFiltered(repo, node.Path).Result()) { + var lfs = new Commands.QueryLFSObject(repo, sha, node.Path).Result(); + layerRevisionPreview.Visibility = Visibility.Visible; + iconRevisionPreview.Data = FindResource("Icon.LFS") as Geometry; + txtRevisionPreview.Text = "LFS SIZE: " + App.Text("Bytes", lfs.Size); + } else if (new Commands.IsBinaryFile(repo, sha, node.Path).Result()) { + layerBinaryPreview.Visibility = Visibility.Visible; + } else { + layerTextPreview.Visibility = Visibility.Visible; + Task.Run(() => { + var lines = new Commands.QueryFileContent(repo, sha, node.Path).Result(); + Dispatcher.Invoke(() => LayoutTextPreview(lines)); + }); + } + break; + case Models.ObjectType.Tag: + layerRevisionPreview.Visibility = Visibility.Visible; + iconRevisionPreview.Data = FindResource("Icon.Tag") as Geometry; + txtRevisionPreview.Text = "TAG: " + node.SHA; + break; + case Models.ObjectType.Commit: + layerRevisionPreview.Visibility = Visibility.Visible; + iconRevisionPreview.Data = FindResource("Icon.Submodule") as Geometry; + txtRevisionPreview.Text = "SUBMODULE: " + node.SHA; + break; + case Models.ObjectType.Tree: + layerRevisionPreview.Visibility = Visibility.Visible; + iconRevisionPreview.Data = FindResource("Icon.Tree") as Geometry; + txtRevisionPreview.Text = "TREE: " + node.SHA; + break; + default: + return; + } + } + + private void OnFilesContextMenuOpening(object sender, ContextMenuEventArgs e) { + var item = treeFiles.FindItem(e.OriginalSource as DependencyObject); + if (item == null) return; + + var node = item.DataContext as FileNode; + if (node == null || node.IsFolder) return; + + var history = new MenuItem(); + history.Header = App.Text("FileHistory"); + history.Click += (o, ev) => { + var viewer = new Views.Histories(repo, node.Path); + viewer.Show(); + ev.Handled = true; + }; + + var blame = new MenuItem(); + blame.Header = App.Text("Blame"); + blame.Click += (obj, ev) => { + var viewer = new Blame(repo, node.Path, sha); + viewer.Show(); + ev.Handled = true; + }; + + var explore = new MenuItem(); + explore.Header = App.Text("RevealFile"); + explore.Click += (o, ev) => { + var full = Path.GetFullPath(repo + "\\" + node.Path); + Process.Start("explorer", $"/select,{full}"); + ev.Handled = true; + }; + + var saveAs = new MenuItem(); + saveAs.Header = App.Text("SaveAs"); + saveAs.IsEnabled = node.Type == Models.ObjectType.Blob; + saveAs.Click += (obj, ev) => { + FolderBrowser.Open(null, App.Text("SaveFileTo"), saveTo => { + var full = Path.Combine(saveTo, Path.GetFileName(node.Path)); + new Commands.SaveRevisionFile(repo, node.Path, sha, full).Exec(); + }); + ev.Handled = true; + }; + + var copyPath = new MenuItem(); + copyPath.Header = App.Text("CopyPath"); + copyPath.Click += (obj, ev) => { + Clipboard.SetText(node.Path); + ev.Handled = true; + }; + + var menu = new ContextMenu(); + menu.Items.Add(history); + menu.Items.Add(blame); + menu.Items.Add(explore); + menu.Items.Add(saveAs); + menu.Items.Add(copyPath); + + menu.IsOpen = true; + e.Handled = true; + } + + private void OnRequestBringIntoView(object sender, RequestBringIntoViewEventArgs e) { + e.Handled = true; + } + #endregion + } +}