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
+ }
+}