using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; namespace SourceGit.Views.Widgets { /// /// 显示提交中的变更列表 /// public partial class CommitChanges : UserControl { private string repo = null; private List range = new List(); private List cachedChanges = new List(); private string filter = null; private bool isSelecting = false; private bool isLFSEnabled = false; public class ChangeNode { public string Path { get; set; } = ""; public Models.Change Change { get; set; } = null; public bool IsExpanded { get; set; } = false; public bool IsFolder => Change == null; public List Children { get; set; } = new List(); } public CommitChanges() { InitializeComponent(); } public void CleanUp() { range.Clear(); cachedChanges.Clear(); modeTree.ItemsSource = new List(); modeGrid.ItemsSource = new List(); modeList.ItemsSource = new List(); } public void SetData(string repo, List range, List changes) { this.repo = repo; this.range = range; this.cachedChanges = changes; this.isLFSEnabled = new Commands.LFS(repo).IsEnabled(); UpdateVisible(); } public void Select(Models.Change change) { isSelecting = true; switch (modeSwitcher.Mode) { case Models.Change.DisplayMode.Tree: var node = FindNodeByChange(modeTree.ItemsSource as List, change); modeTree.Select(node); break; case Models.Change.DisplayMode.List: modeList.SelectedItem = change; modeList.ScrollIntoView(change); break; case Models.Change.DisplayMode.Grid: modeGrid.SelectedItem = change; modeGrid.ScrollIntoView(change); break; } isSelecting = false; } private void UpdateVisible() { Task.Run(() => { // 筛选出可见的列表 List visible; if (string.IsNullOrEmpty(filter)) { visible = cachedChanges; } else { visible = cachedChanges.Where(x => x.Path.ToUpper().Contains(filter)).ToList(); } // 排序 visible.Sort((l, r) => l.Path.CompareTo(r.Path)); // 生成树节点 var nodes = new List(); var folders = new Dictionary(); var expanded = visible.Count <= 50; foreach (var c in visible) { var sepIdx = c.Path.IndexOf('/'); if (sepIdx == -1) { nodes.Add(new ChangeNode() { Path = c.Path, Change = c, IsExpanded = false }); } else { ChangeNode lastFolder = null; var start = 0; while (sepIdx != -1) { var folder = c.Path.Substring(0, sepIdx); if (folders.ContainsKey(folder)) { lastFolder = folders[folder]; } else if (lastFolder == null) { lastFolder = new ChangeNode() { Path = folder, Change = null, IsExpanded = expanded }; nodes.Add(lastFolder); folders.Add(folder, lastFolder); } else { var cur = new ChangeNode() { Path = folder, Change = null, IsExpanded = expanded }; folders.Add(folder, cur); lastFolder.Children.Add(cur); lastFolder = cur; } start = sepIdx + 1; sepIdx = c.Path.IndexOf('/', start); } lastFolder.Children.Add(new ChangeNode() { Path = c.Path, Change = c, IsExpanded = false }); } } folders.Clear(); SortFileNodes(nodes); Dispatcher.Invoke(() => { modeTree.ItemsSource = nodes; modeList.ItemsSource = visible; modeGrid.ItemsSource = visible; UpdateMode(); }); }); } private ChangeNode FindNodeByChange(List nodes, Models.Change change) { if (nodes == null || nodes.Count == 0) return null; foreach (var node in nodes) { if (node.IsFolder) { var found = FindNodeByChange(node.Children, change); if (found != null) return found; } else if (node.Change == change) { return node; } } return null; } 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 UpdateMode() { var mode = modeSwitcher.Mode; if (modeTree != null) { if (mode == Models.Change.DisplayMode.Tree) { modeTree.Visibility = Visibility.Visible; } else { modeTree.Visibility = Visibility.Collapsed; } } if (modeList != null) { if (mode == Models.Change.DisplayMode.List) { modeList.Visibility = Visibility.Visible; modeList.Columns[1].Width = DataGridLength.SizeToCells; modeList.Columns[1].Width = DataGridLength.Auto; } else { modeList.Visibility = Visibility.Collapsed; } } if (modeGrid != null) { if (mode == Models.Change.DisplayMode.Grid) { modeGrid.Visibility = Visibility.Visible; modeGrid.Columns[1].Width = DataGridLength.SizeToCells; modeGrid.Columns[1].Width = DataGridLength.Auto; modeGrid.Columns[2].Width = DataGridLength.SizeToCells; modeGrid.Columns[2].Width = DataGridLength.Auto; } else { modeGrid.Visibility = Visibility.Collapsed; } } } private void OpenChangeDiff(Models.Change change) { var revisions = new string[] { "", "" }; if (range.Count == 2) { revisions[0] = range[0].SHA; revisions[1] = range[1].SHA; } else { revisions[0] = $"{range[0].SHA}^"; revisions[1] = range[0].SHA; if (range[0].Parents.Count == 0) revisions[0] = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"; } diffViewer.Diff(repo, new DiffViewer.Option() { RevisionRange = revisions, Path = change.Path, OrgPath = change.OriginalPath, UseLFS = isLFSEnabled, }); } private void OpenChangeContextMenu(Models.Change change) { var menu = new ContextMenu(); var path = change.Path; if (change.Index != Models.Change.Status.Deleted) { var history = new MenuItem(); history.Header = App.Text("FileHistory"); history.Click += (o, ev) => { var viewer = new Views.Histories(repo, path); viewer.Show(); ev.Handled = true; }; var blame = new MenuItem(); blame.Header = App.Text("Blame"); blame.Visibility = range.Count == 1 ? Visibility.Visible : Visibility.Collapsed; blame.Click += (obj, ev) => { var viewer = new Blame(repo, path, range[0].SHA); viewer.Show(); ev.Handled = true; }; var explore = new MenuItem(); explore.Header = App.Text("RevealFile"); explore.Click += (o, ev) => { var full = Path.GetFullPath(repo + "\\" + path); Process.Start("explorer", $"/select,{full}"); ev.Handled = true; }; menu.Items.Add(history); menu.Items.Add(blame); menu.Items.Add(explore); } var copyPath = new MenuItem(); copyPath.Header = App.Text("CopyPath"); copyPath.Click += (obj, ev) => { Clipboard.SetText(path); }; menu.Items.Add(copyPath); menu.IsOpen = true; } private void OnDisplayModeChanged(object sender, RoutedEventArgs e) { UpdateMode(); } private void SearchFilterChanged(object sender, TextChangedEventArgs e) { var edit = sender as Controls.TextEdit; filter = edit.Text.ToUpper(); UpdateVisible(); } private void OnRequestBringIntoView(object sender, RequestBringIntoViewEventArgs e) { if (!isSelecting) e.Handled = true; } private void OnTreeSelectionChanged(object sender, RoutedEventArgs e) { if (modeSwitcher.Mode != Models.Change.DisplayMode.Tree) return; diffViewer.Reset(); if (modeTree.Selected.Count == 0) return; var change = (modeTree.Selected[0] as ChangeNode).Change; if (change == null) return; OpenChangeDiff(change); } private void OnListSelectionChanged(object sender, SelectionChangedEventArgs e) { if (modeSwitcher.Mode != Models.Change.DisplayMode.List) return; diffViewer.Reset(); var change = (sender as DataGrid).SelectedItem as Models.Change; if (change == null) return; OpenChangeDiff(change); } private void OnGridSelectionChanged(object sender, SelectionChangedEventArgs e) { if (modeSwitcher.Mode != Models.Change.DisplayMode.Grid) return; diffViewer.Reset(); var change = (sender as DataGrid).SelectedItem as Models.Change; if (change == null) return; OpenChangeDiff(change); } private void OnTreeContextMenuOpening(object sender, ContextMenuEventArgs e) { var item = sender as Controls.TreeItem; if (item == null) return; var node = item.DataContext as ChangeNode; if (node == null || node.IsFolder) return; OpenChangeContextMenu(node.Change); e.Handled = true; } private void OnDataGridContextMenuOpening(object sender, ContextMenuEventArgs e) { var row = sender as DataGridRow; if (row == null) return; var change = row.Item as Models.Change; if (change == null) return; OpenChangeContextMenu(change); e.Handled = true; } private void OnListSizeChanged(object sender, SizeChangedEventArgs e) { if (modeSwitcher.Mode != Models.Change.DisplayMode.List) return; int last = modeList.Columns.Count - 1; double offset = 0; for (int i = 0; i < last; i++) offset += modeList.Columns[i].ActualWidth; modeList.Columns[last].MinWidth = Math.Max(layerChanges.ActualWidth - offset, 10); modeList.UpdateLayout(); } private void OnGridSizeChanged(object sender, SizeChangedEventArgs e) { if (modeSwitcher.Mode != Models.Change.DisplayMode.Grid) return; int last = modeGrid.Columns.Count - 1; double offset = 0; for (int i = 0; i < last; i++) offset += modeGrid.Columns[i].ActualWidth; modeGrid.Columns[last].MinWidth = Math.Max(layerChanges.ActualWidth - offset, 10); modeGrid.UpdateLayout(); } } }