From 1de529237a45c5dd5b3842c872a3861088fe03cb Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 5 Jan 2021 16:59:35 +0800 Subject: [PATCH] feature: reload diff content when working copy changed --- src/UI/DiffViewer.xaml.cs | 264 ++++++++++++++++++++++++----------- src/UI/Histories.xaml.cs | 2 +- src/UI/WorkingCopy.xaml | 12 +- src/UI/WorkingCopy.xaml.cs | 275 ++++++++++++++++++++++++------------- 4 files changed, 371 insertions(+), 182 deletions(-) diff --git a/src/UI/DiffViewer.xaml.cs b/src/UI/DiffViewer.xaml.cs index b804a410..77f9323e 100644 --- a/src/UI/DiffViewer.xaml.cs +++ b/src/UI/DiffViewer.xaml.cs @@ -22,7 +22,9 @@ namespace SourceGit.UI { public static readonly Brush BG_DELETED = new SolidColorBrush(Color.FromArgb(60, 255, 0, 0)); public static readonly Brush BG_NORMAL = Brushes.Transparent; - private List lineChanges = null; + private Git.Repository lastRepo = null; + private Option lastOpts = null; + private List lastChanges = null; private List editors = new List(); /// @@ -73,9 +75,8 @@ namespace SourceGit.UI { /// public void Reset() { mask.Visibility = Visibility.Visible; - lineChanges = null; - foreach (var editor in editors) editorContainer.Children.Remove(editor); - editors.Clear(); + ClearCache(); + ClearEditor(); } /// @@ -91,11 +92,11 @@ namespace SourceGit.UI { sizeChange.Visibility = Visibility.Collapsed; noChange.Visibility = Visibility.Collapsed; - editorContainer.Children.Clear(); - editorLines.Children.Clear(); - editorLines.ColumnDefinitions.Clear(); - editors.Clear(); - lineChanges = null; + ClearEditor(); + ClearCache(); + + lastRepo = repo; + lastOpts = opts; Task.Run(() => { var args = $"{opts.ExtraArgs} "; @@ -122,7 +123,57 @@ namespace SourceGit.UI { if (rs.IsBinary) { SetBinaryChange(Git.Diff.GetSizeChange(repo, opts.RevisionRange, opts.Path, opts.OrgPath)); } else if (rs.Lines.Count > 0) { - lineChanges = rs.Lines; + lastChanges = rs.Lines; + SetTextChange(); + } else { + SetSame(); + } + }); + } + + /// + /// Reload diff content with last repository and options. + /// + public void Reload() { + if (lastRepo == null) { + Reset(); + return; + } + + loading.Visibility = Visibility.Visible; + mask.Visibility = Visibility.Collapsed; + sizeChange.Visibility = Visibility.Collapsed; + noChange.Visibility = Visibility.Collapsed; + + var repo = lastRepo; + var opts = lastOpts; + + Task.Run(() => { + var args = $"{opts.ExtraArgs} "; + if (opts.RevisionRange.Length > 0) args += $"{opts.RevisionRange[0]} "; + if (opts.RevisionRange.Length > 1) args += $"{opts.RevisionRange[1]} "; + + args += "-- "; + + if (!string.IsNullOrEmpty(opts.OrgPath)) args += $"\"{opts.OrgPath}\" "; + args += $"\"{opts.Path}\""; + + if (repo.IsLFSFiltered(opts.Path)) { + var lc = Git.Diff.GetLFSChange(repo, args); + if (lc.IsValid) { + SetLFSChange(lc); + } else { + SetSame(); + } + + return; + } + + var rs = Git.Diff.GetTextChange(repo, args); + if (rs.IsBinary) { + SetBinaryChange(Git.Diff.GetSizeChange(repo, opts.RevisionRange, opts.Path, opts.OrgPath)); + } else if (rs.Lines.Count > 0) { + lastChanges = rs.Lines; SetTextChange(); } else { SetSame(); @@ -151,7 +202,7 @@ namespace SourceGit.UI { /// /// private void SetTextChange() { - if (lineChanges == null) return; + if (lastChanges == null) return; var fgCommon = FindResource("Brush.FG") as Brush; var fgIndicator = FindResource("Brush.FG2") as Brush; @@ -161,7 +212,7 @@ namespace SourceGit.UI { if (App.Setting.UI.UseCombinedDiff) { var blocks = new List(); - foreach (var line in lineChanges) { + foreach (var line in lastChanges) { var block = new ChangeBlock(); block.Content = line.Content; block.Mode = line.Mode; @@ -181,36 +232,52 @@ namespace SourceGit.UI { loading.Visibility = Visibility.Collapsed; textChangeOptions.Visibility = Visibility.Visible; + var createEditor = editors.Count == 0; var lineNumberWidth = CalcLineNumberColWidth(lastOldLine, lastNewLine); var minWidth = editorContainer.ActualWidth - lineNumberWidth * 2; - if (editorContainer.ActualHeight < lineChanges.Count * 16) minWidth -= 8; - var editor = CreateTextEditor(new string[] { "OldLine", "NewLine" }); + if (editorContainer.ActualHeight < lastChanges.Count * 16) minWidth -= 8; + + DataGrid editor; + if (createEditor) { + editor = CreateTextEditor(new string[] { "OldLine", "NewLine" }); + editor.SetValue(Grid.ColumnSpanProperty, 2); + + editorLines.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(lineNumberWidth) }); + editorLines.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(lineNumberWidth) }); + + for (int i = 0; i < 2; i++) { + var split = new Rectangle(); + split.Width = 1; + split.Fill = FindResource("Brush.Border2") as Brush; + split.HorizontalAlignment = HorizontalAlignment.Right; + Grid.SetColumn(split, i); + + editorLines.Children.Add(split); + } + } else { + editor = editors[0] as DataGrid; + editorLines.ColumnDefinitions[0].Width = new GridLength(lineNumberWidth); + editorLines.ColumnDefinitions[0].Width = new GridLength(lineNumberWidth); + } + editor.Columns[0].Width = new DataGridLength(lineNumberWidth, DataGridLengthUnitType.Pixel); editor.Columns[1].Width = new DataGridLength(lineNumberWidth, DataGridLengthUnitType.Pixel); editor.Columns[2].MinWidth = minWidth; editor.ItemsSource = blocks; - editor.SetValue(Grid.ColumnSpanProperty, 2); - editorContainer.Children.Add(editor); - editors.Add(editor); - editorLines.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(lineNumberWidth) }); - editorLines.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(lineNumberWidth) }); - - for (int i = 0; i < 2; i++) { - var split = new Rectangle(); - split.Width = 1; - split.Fill = FindResource("Brush.Border2") as Brush; - split.HorizontalAlignment = HorizontalAlignment.Right; - Grid.SetColumn(split, i); - - editorLines.Children.Add(split); + if (createEditor) { + editorContainer.Children.Add(editor); + editors.Add(editor); + } else { + editor.UpdateLayout(); + editorLines.UpdateLayout(); } }); } else { var oldSideBlocks = new List(); var newSideBlocks = new List(); - foreach (var line in lineChanges) { + foreach (var line in lastChanges) { var block = new ChangeBlock(); block.Content = line.Content; block.Mode = line.Mode; @@ -244,42 +311,63 @@ namespace SourceGit.UI { loading.Visibility = Visibility.Collapsed; textChangeOptions.Visibility = Visibility.Visible; + var createEditor = editors.Count == 0; var lineNumberWidth = CalcLineNumberColWidth(lastOldLine, lastNewLine); var minWidth = editorContainer.ActualWidth / 2 - lineNumberWidth; if (editorContainer.ActualHeight < newSideBlocks.Count * 16) minWidth -= 8; - var oldEditor = CreateTextEditor(new string[] { "OldLine" }); - oldEditor.SetValue(Grid.ColumnProperty, 0); - oldEditor.AddHandler(ScrollViewer.ScrollChangedEvent, new ScrollChangedEventHandler(OnTwoSidesScroll)); + DataGrid oldEditor = null; + DataGrid newEditor = null; + + if (createEditor) { + oldEditor = CreateTextEditor(new string[] { "OldLine" }); + oldEditor.SetValue(Grid.ColumnProperty, 0); + oldEditor.AddHandler(ScrollViewer.ScrollChangedEvent, new ScrollChangedEventHandler(OnTwoSidesScroll)); + + newEditor = CreateTextEditor(new string[] { "NewLine" }); + newEditor.SetValue(Grid.ColumnProperty, 1); + newEditor.AddHandler(ScrollViewer.ScrollChangedEvent, new ScrollChangedEventHandler(OnTwoSidesScroll)); + + editorLines.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(lineNumberWidth) }); + editorLines.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) }); + editorLines.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(lineNumberWidth) }); + editorLines.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) }); + + for (int i = 0; i < 3; i++) { + var split = new Rectangle(); + split.Width = 1; + split.Fill = FindResource("Brush.Border2") as Brush; + split.HorizontalAlignment = HorizontalAlignment.Right; + Grid.SetColumn(split, i); + editorLines.Children.Add(split); + } + } else { + oldEditor = editors[0]; + newEditor = editors[1]; + + editorLines.ColumnDefinitions[0].Width = new GridLength(lineNumberWidth); + editorLines.ColumnDefinitions[1].Width = new GridLength(1, GridUnitType.Star); + editorLines.ColumnDefinitions[2].Width = new GridLength(lineNumberWidth); + editorLines.ColumnDefinitions[3].Width = new GridLength(1, GridUnitType.Star); + } + oldEditor.Columns[0].Width = new DataGridLength(lineNumberWidth, DataGridLengthUnitType.Pixel); oldEditor.Columns[1].MinWidth = minWidth; oldEditor.ItemsSource = oldSideBlocks; - var newEditor = CreateTextEditor(new string[] { "NewLine" }); - newEditor.SetValue(Grid.ColumnProperty, 1); - newEditor.AddHandler(ScrollViewer.ScrollChangedEvent, new ScrollChangedEventHandler(OnTwoSidesScroll)); newEditor.Columns[0].Width = new DataGridLength(lineNumberWidth, DataGridLengthUnitType.Pixel); newEditor.Columns[1].MinWidth = minWidth; newEditor.ItemsSource = newSideBlocks; - editorContainer.Children.Add(oldEditor); - editorContainer.Children.Add(newEditor); - - editors.Add(oldEditor); - editors.Add(newEditor); - - editorLines.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(lineNumberWidth) }); - editorLines.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) }); - editorLines.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(lineNumberWidth) }); - editorLines.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) }); - - for (int i = 0; i < 3; i++) { - var split = new Rectangle(); - split.Width = 1; - split.Fill = FindResource("Brush.Border2") as Brush; - split.HorizontalAlignment = HorizontalAlignment.Right; - Grid.SetColumn(split, i); - editorLines.Children.Add(split); + if (createEditor) { + editorContainer.Children.Add(oldEditor); + editorContainer.Children.Add(newEditor); + editors.Add(oldEditor); + editors.Add(newEditor); + } else { + oldEditor.UpdateLayout(); + newEditor.UpdateLayout(); + editorLines.UpdateLayout(); } }); } @@ -491,10 +579,28 @@ namespace SourceGit.UI { return formatted.Width + 16; } + + /// + /// Clear cached data. + /// + private void ClearCache() { + lastRepo = null; + lastOpts = null; + lastChanges = null; + } + + /// + /// Clear editor. + /// + private void ClearEditor() { + editorContainer.Children.Clear(); + editorLines.Children.Clear(); + editorLines.ColumnDefinitions.Clear(); + editors.Clear(); + } #endregion #region EVENTS - /// /// Auto fit text change diff size. /// @@ -513,18 +619,23 @@ namespace SourceGit.UI { editor.Columns[2].Width = DataGridLength.SizeToCells; editor.UpdateLayout(); } else { - var offOld = editors[0].NonFrozenColumnsViewportHorizontalOffset; - var offNew = editors[1].NonFrozenColumnsViewportHorizontalOffset; + var oldEditor = editors[0]; + var newEditor = editors[1]; + + var offOld = oldEditor.NonFrozenColumnsViewportHorizontalOffset; + var offNew = newEditor.NonFrozenColumnsViewportHorizontalOffset; var minWidth = total / 2 - Math.Min(offOld, offNew); - var scroller = GetVisualChild(editors[0]); + var scroller = GetVisualChild(oldEditor); if (scroller != null && scroller.ComputedVerticalScrollBarVisibility == Visibility.Visible) minWidth -= 8; - editors[0].Columns[1].MinWidth = minWidth; - editors[0].Columns[1].Width = DataGridLength.SizeToCells; - editors[1].Columns[1].MinWidth = minWidth; - editors[1].Columns[1].Width = DataGridLength.SizeToCells; - editors[0].UpdateLayout(); - editors[1].UpdateLayout(); + + oldEditor.Columns[1].MinWidth = minWidth; + oldEditor.Columns[1].Width = DataGridLength.SizeToCells; + oldEditor.UpdateLayout(); + + newEditor.Columns[1].MinWidth = minWidth; + newEditor.Columns[1].Width = DataGridLength.SizeToCells; + newEditor.UpdateLayout(); } } @@ -543,19 +654,16 @@ namespace SourceGit.UI { /// /// private void OnTwoSidesScroll(object sender, ScrollChangedEventArgs e) { - if (e.VerticalChange != 0) { - foreach (var editor in editors) { - var scroller = GetVisualChild(editor); - if (scroller != null && scroller.VerticalOffset != e.VerticalOffset) { - scroller.ScrollToVerticalOffset(e.VerticalOffset); - } + foreach (var editor in editors) { + var scroller = GetVisualChild(editor); + if (scroller == null) continue; + + if (e.VerticalChange != 0 && scroller.VerticalOffset != e.VerticalOffset) { + scroller.ScrollToVerticalOffset(e.VerticalOffset); } - } else { - foreach (var editor in editors) { - var scroller = GetVisualChild(editor); - if (scroller != null && scroller.HorizontalOffset != e.HorizontalOffset) { - scroller.ScrollToHorizontalOffset(e.HorizontalOffset); - } + + if (e.HorizontalChange != 0 && scroller.HorizontalOffset != e.HorizontalOffset) { + scroller.ScrollToHorizontalOffset(e.HorizontalOffset); } } } @@ -624,11 +732,7 @@ namespace SourceGit.UI { private void ChangeDiffMode(object sender, RoutedEventArgs e) { if (!IsLoaded) return; - editorContainer.Children.Clear(); - editorLines.Children.Clear(); - editorLines.ColumnDefinitions.Clear(); - editors.Clear(); - + ClearEditor(); SetTextChange(); } diff --git a/src/UI/Histories.xaml.cs b/src/UI/Histories.xaml.cs index a481c259..b6d9ec20 100644 --- a/src/UI/Histories.xaml.cs +++ b/src/UI/Histories.xaml.cs @@ -96,7 +96,7 @@ namespace SourceGit.UI { Dispatcher.Invoke(() => { txtSearch.Text = ""; - commitList.ItemsSource = new List(cachedCommits); + commitList.ItemsSource = cachedCommits; SetLoadingEnabled(false); }); } diff --git a/src/UI/WorkingCopy.xaml b/src/UI/WorkingCopy.xaml index 969004f1..d6d93c7e 100644 --- a/src/UI/WorkingCopy.xaml +++ b/src/UI/WorkingCopy.xaml @@ -72,7 +72,8 @@ Children { get; set; } = new List(); + public ObservableCollection Children { get; set; } = new ObservableCollection(); } /// @@ -40,14 +42,39 @@ namespace SourceGit.UI { public string CommitMessage { get; set; } /// - /// Has conflict object? + /// Cached unstaged changes in list/grid view. /// - private bool hasConflict = false; + public ObservableCollection UnstagedListData { get; set; } + + /// + /// Cached unstaged changes in TreeView. + /// + public ObservableCollection UnstagedTreeData { get; set; } + + /// + /// Cached staged changes in list/grid view. + /// + public ObservableCollection StagedListData { get; set; } + + /// + /// Cached staged changes in TreeView. + /// + public ObservableCollection StagedTreeData { get; set; } + + /// + /// Last view change + /// + public Git.Change LastViewChange { get; set; } /// /// Constructor. /// public WorkingCopy() { + UnstagedListData = new ObservableCollection(); + UnstagedTreeData = new ObservableCollection(); + StagedListData = new ObservableCollection(); + StagedTreeData = new ObservableCollection(); + InitializeComponent(); } @@ -58,24 +85,35 @@ namespace SourceGit.UI { public bool SetData(List changes) { List staged = new List(); List unstaged = new List(); - hasConflict = false; + bool hasConflict = false; + bool removeLastViewChange = true; foreach (var c in changes) { hasConflict = hasConflict || c.IsConflit; - if (c.Index != Git.Change.Status.None && c.Index != Git.Change.Status.Untracked) { + if (c.IsAddedToIndex) { staged.Add(c); + if (LastViewChange != null && LastViewChange.IsAddedToIndex && c.Path == LastViewChange.Path) { + LastViewChange = c; + removeLastViewChange = false; + } } if (c.WorkTree != Git.Change.Status.None) { unstaged.Add(c); + if (LastViewChange != null && !LastViewChange.IsAddedToIndex && c.Path == LastViewChange.Path) { + LastViewChange = c; + removeLastViewChange = false; + } } } - SetData(unstaged, true); - SetData(staged, false); + if (removeLastViewChange) LastViewChange = null; Dispatcher.Invoke(() => { + UpdateData(unstaged, UnstagedListData, UnstagedTreeData); + UpdateData(staged, StagedListData, StagedTreeData); + var current = Repo.CurrentBranch(); if (current != null && !string.IsNullOrEmpty(current.Upstream) && chkAmend.IsChecked != true) { btnCommitAndPush.Visibility = Visibility.Visible; @@ -83,13 +121,55 @@ namespace SourceGit.UI { btnCommitAndPush.Visibility = Visibility.Collapsed; } - mergePanel.Visibility = Visibility.Collapsed; - diffViewer.Reset(); + if (LastViewChange != null) { + if (LastViewChange.IsConflit) { + mergePanel.Visibility = Visibility.Visible; + diffViewer.Reset(); + } else { + mergePanel.Visibility = Visibility.Collapsed; + diffViewer.Reload(); + } + } else { + mergePanel.Visibility = Visibility.Collapsed; + diffViewer.Reset(); + } }); return hasConflict; } + /// + /// Update data. + /// + /// + /// + /// + public void UpdateData(List changes, ObservableCollection list, ObservableCollection tree) { + for (int i = list.Count - 1; i >= 0; i--) { + var exist = list[i]; + if (changes.FirstOrDefault(one => one.Path == exist.Path) != null) continue; + + list.RemoveAt(i); + RemoveTreeNode(tree, exist); + } + + foreach (var c in changes) { + if (list.FirstOrDefault(one => one.Path == c.Path) != null) continue; + + bool added = false; + for (int i = 0; i < list.Count; i++) { + if (c.Path.CompareTo(list[i].Path) < 0) { + list.Insert(i, c); + added = true; + break; + } + } + if (!added) list.Add(c); + + InsertTreeNode(tree, c); + } + } + /// /// Try to load merge message. /// @@ -116,10 +196,10 @@ namespace SourceGit.UI { /// public void Cleanup() { Repo = null; - unstagedList.ItemsSource = null; - unstagedList.ItemsSource = null; - stageList.ItemsSource = null; - stageTree.ItemsSource = null; + UnstagedListData.Clear(); + UnstagedTreeData.Clear(); + StagedListData.Clear(); + StagedTreeData.Clear(); diffViewer.Reset(); } @@ -143,6 +223,8 @@ namespace SourceGit.UI { return; } + LastViewChange = node.Change; + DiffViewer.Option opt; switch (node.Change.WorkTree) { case Git.Change.Status.Added: @@ -174,6 +256,8 @@ namespace SourceGit.UI { return; } + LastViewChange = change; + DiffViewer.Option opt; switch (change.WorkTree) { case Git.Change.Status.Added: @@ -543,6 +627,8 @@ namespace SourceGit.UI { var node = selected[0].DataContext as Node; if (!node.IsFile) return; + LastViewChange = node.Change; + diffViewer.Diff(Repo, new DiffViewer.Option() { ExtraArgs = "--cached", Path = node.FilePath, @@ -563,6 +649,7 @@ namespace SourceGit.UI { if (selected.Count != 1) return; var change = selected[0] as Git.Change; + LastViewChange = change; diffViewer.Diff(Repo, new DiffViewer.Option() { ExtraArgs = "--cached", Path = change.Path, @@ -816,15 +903,16 @@ namespace SourceGit.UI { } private async void Commit(object sender, RoutedEventArgs e) { - var amend = chkAmend.IsChecked == true; - - Repo.RecordCommitMessage(CommitMessage); - - if (hasConflict) { - App.RaiseError("You have unsolved conflicts in your working copy!"); - return; + foreach (var c in UnstagedListData) { + if (c.IsConflit) { + App.RaiseError("You have unsolved conflicts in your working copy!"); + return; + } } + var amend = chkAmend.IsChecked == true; + Repo.RecordCommitMessage(CommitMessage); + if (stageTree.Items.Count == 0) { App.RaiseError("Nothing to commit!"); return; @@ -838,15 +926,16 @@ namespace SourceGit.UI { } private async void CommitAndPush(object sender, RoutedEventArgs e) { - var amend = chkAmend.IsChecked == true; - - Repo.RecordCommitMessage(CommitMessage); - - if (hasConflict) { - App.RaiseError("You have unsolved conflicts in your working copy!"); - return; + foreach (var c in UnstagedListData) { + if (c.IsConflit) { + App.RaiseError("You have unsolved conflicts in your working copy!"); + return; + } } + var amend = chkAmend.IsChecked == true; + Repo.RecordCommitMessage(CommitMessage); + if (stageTree.Items.Count == 0) { App.RaiseError("Nothing to commit!"); return; @@ -966,69 +1055,75 @@ namespace SourceGit.UI { Helpers.TreeViewHelper.SelectWholeTree(tree); } - private void SetData(List changes, bool unstaged) { - List source = new List(); - Dictionary folders = new Dictionary(); - bool isExpendDefault = changes.Count <= 50; + private Node InsertTreeNode(ObservableCollection nodes, string name, string path, bool isFile = false, Git.Change change = null) { + Node node = new Node(); + node.Name = name; + node.FilePath = path; + node.IsFile = isFile; + node.Change = change; - foreach (var c in changes) { - var subs = c.Path.Split(new char[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries); - if (subs.Length == 1) { - Node node = new Node(); - node.FilePath = c.Path; - node.IsFile = true; - node.Name = c.Path; - node.Change = c; - source.Add(node); - } else { - Node lastFolder = null; - var folder = ""; - for (int i = 0; i < subs.Length - 1; i++) { - folder += (subs[i] + "/"); - if (folders.ContainsKey(folder)) { - lastFolder = folders[folder]; - } else if (lastFolder == null) { - lastFolder = new Node(); - lastFolder.FilePath = folder; - lastFolder.Name = subs[i]; - lastFolder.IsNodeExpanded = isExpendDefault; - source.Add(lastFolder); - folders.Add(folder, lastFolder); - } else { - var folderNode = new Node(); - folderNode.FilePath = folder; - folderNode.Name = subs[i]; - folderNode.IsNodeExpanded = isExpendDefault; - folders.Add(folder, folderNode); - lastFolder.Children.Add(folderNode); - lastFolder = folderNode; - } + bool isAdded = false; + if (node.IsFile) { + for (var i = 0; i < nodes.Count; i++) { + var cur = nodes[i]; + if (!cur.IsFile) continue; + if (node.FilePath.CompareTo(cur.FilePath) > 0) continue; + nodes.Insert(i, node); + isAdded = true; + break; + } + } else { + for (var i = 0; i < nodes.Count; i++) { + var cur = nodes[i]; + if (cur.IsFile || node.FilePath.CompareTo(cur.FilePath) < 0) { + nodes.Insert(i, node); + isAdded = true; + break; } - - Node node = new Node(); - node.FilePath = c.Path; - node.Name = subs[subs.Length - 1]; - node.IsFile = true; - node.Change = c; - lastFolder.Children.Add(node); } } - folders.Clear(); - SortTreeNodes(source); - - Dispatcher.Invoke(() => { - if (unstaged) { - unstagedList.ItemsSource = changes; - unstagedTree.ItemsSource = source; - } else { - stageList.ItemsSource = changes; - stageTree.ItemsSource = source; - } - }); + if (!isAdded) nodes.Add(node); + return node; } - private Node FindNodeByPath(List nodes, string filePath) { + private void InsertTreeNode(ObservableCollection nodes, Git.Change change) { + string[] subs = change.Path.Split(new char[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries); + if (subs.Length == 1) { + InsertTreeNode(nodes, change.Path, change.Path, true, change); + } else { + Node last = nodes.FirstOrDefault(o => o.Name == subs[0]); + if (last == null) last = InsertTreeNode(nodes, subs[0], subs[0]); + + for (int i = 1; i < subs.Length - 1; i++) { + var p = last.Children.FirstOrDefault(o => o.Name == subs[i]); + if (p == null) p = InsertTreeNode(last.Children, subs[i], last.FilePath + "/" + subs[i]); + last = p; + } + + InsertTreeNode(last.Children, subs[subs.Length - 1], change.Path, true, change); + } + } + + private bool RemoveTreeNode(ObservableCollection nodes, Git.Change change) { + for (int i = nodes.Count - 1; i >= 0; i--) { + if (nodes[i].FilePath == change.Path) { + nodes.RemoveAt(i); + return true; + } + + if (nodes[i].IsFile) continue; + + if (RemoveTreeNode(nodes[i].Children, change)) { + if (nodes[i].Children.Count == 0) nodes.RemoveAt(i); + return true; + } + } + + return false; + } + + private Node FindNodeByPath(ObservableCollection nodes, string filePath) { foreach (var node in nodes) { if (node.FilePath == filePath) return node; var found = FindNodeByPath(node.Children, filePath); @@ -1037,20 +1132,6 @@ namespace SourceGit.UI { return null; } - private void SortTreeNodes(List list) { - list.Sort((l, r) => { - if (l.IsFile) { - return r.IsFile ? l.FilePath.CompareTo(r.FilePath) : 1; - } else { - return r.IsFile ? -1 : l.FilePath.CompareTo(r.FilePath); - } - }); - - foreach (var sub in list) { - if (sub.Children.Count > 0) SortTreeNodes(sub.Children); - } - } - private void TreeMouseWheel(object sender, MouseWheelEventArgs e) { var scroll = Helpers.TreeViewHelper.GetScrollViewer(sender as TreeView); if (scroll == null) return;