diff --git a/ILSpy/DecompilationOptions.cs b/ILSpy/DecompilationOptions.cs index 162fbe29c..bada56620 100644 --- a/ILSpy/DecompilationOptions.cs +++ b/ILSpy/DecompilationOptions.cs @@ -51,7 +51,15 @@ namespace ICSharpCode.ILSpy /// Gets the settings for the decompiler. /// public DecompilerSettings DecompilerSettings { get; set; } - + + /// + /// Gets/sets an optional state of a decompiler text view. + /// + /// + /// This state is used to restore test view's state when decompilation is started by Go Back/Forward action. + /// + public ICSharpCode.ILSpy.TextView.DecompilerTextViewState TextViewState { get; set; } + public DecompilationOptions() { this.DecompilerSettings = DecompilerSettingsPanel.CurrentDecompilerSettings; diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index 7243d4001..abff3d499 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -46,7 +46,8 @@ namespace ICSharpCode.ILSpy /// partial class MainWindow : Window { - NavigationHistory history = new NavigationHistory(); + NavigationHistory, DecompilerTextViewState>> history = + new NavigationHistory, DecompilerTextViewState>>(); ILSpySettings spySettings; SessionSettings sessionSettings; AssemblyListManager assemblyListManager; @@ -254,7 +255,7 @@ namespace ICSharpCode.ILSpy { if (e.OldItems != null) foreach (LoadedAssembly asm in e.OldItems) - history.RemoveAll(n => n.AncestorsAndSelf().OfType().Any(a => a.LoadedAssembly == asm)); + history.RemoveAll(n => n.Item1.Any(nd => nd.AncestorsAndSelf().OfType().Any(a => a.LoadedAssembly == asm))); } void LoadInitialAssemblies() @@ -284,7 +285,7 @@ namespace ICSharpCode.ILSpy { RefreshTreeViewFilter(); if (e.PropertyName == "Language") { - TreeView_SelectionChanged(null, null); + DecompileSelectedNodes(); } } @@ -311,7 +312,7 @@ namespace ICSharpCode.ILSpy if (obj != null) { SharpTreeNode oldNode = treeView.SelectedItem as SharpTreeNode; if (oldNode != null && recordNavigationInHistory) - history.Record(oldNode); + history.Record(Tuple.Create(treeView.SelectedItems.OfType().ToList(), decompilerTextView.GetState())); // Set both the selection and focus to ensure that keyboard navigation works as expected. treeView.FocusNode(obj); treeView.SelectedItem = obj; @@ -426,12 +427,22 @@ namespace ICSharpCode.ILSpy #region Decompile (TreeView_SelectionChanged) void TreeView_SelectionChanged(object sender, SelectionChangedEventArgs e) { + DecompileSelectedNodes(); + } + + private bool ignoreDecompilationRequests; + + private void DecompileSelectedNodes(DecompilerTextViewState state = null) + { + if (ignoreDecompilationRequests) + return; + if (treeView.SelectedItems.Count == 1) { ILSpyTreeNode node = treeView.SelectedItem as ILSpyTreeNode; if (node != null && node.View(decompilerTextView)) return; } - decompilerTextView.Decompile(this.CurrentLanguage, this.SelectedNodes, new DecompilationOptions()); + decompilerTextView.Decompile(this.CurrentLanguage, this.SelectedNodes, new DecompilationOptions() { TextViewState = state }); } void SaveCommandExecuted(object sender, ExecutedRoutedEventArgs e) @@ -447,7 +458,7 @@ namespace ICSharpCode.ILSpy public void RefreshDecompiledView() { - TreeView_SelectionChanged(null, null); + DecompileSelectedNodes(); } public DecompilerTextView TextView { @@ -478,7 +489,7 @@ namespace ICSharpCode.ILSpy { if (history.CanNavigateBack) { e.Handled = true; - SelectNode(history.GoBack(treeView.SelectedItem as SharpTreeNode), false); + NavigateHistory(false); } } @@ -492,9 +503,27 @@ namespace ICSharpCode.ILSpy { if (history.CanNavigateForward) { e.Handled = true; - SelectNode(history.GoForward(treeView.SelectedItem as SharpTreeNode), false); + NavigateHistory(true); } } + + void NavigateHistory(bool forward) + { + var currentSelection = treeView.SelectedItems.OfType().ToList(); + var state = decompilerTextView.GetState(); + var combinedState = Tuple.Create(currentSelection, state); + var newState = forward ? history.GoForward(combinedState) : history.GoBack(combinedState); + + this.ignoreDecompilationRequests = true; + treeView.SelectedItems.Clear(); + foreach (var node in newState.Item1) + { + treeView.SelectedItems.Add(node); + } + ignoreDecompilationRequests = false; + DecompileSelectedNodes(newState.Item2); + } + #endregion #region Analyzer diff --git a/ILSpy/NavigationHistory.cs b/ILSpy/NavigationHistory.cs index f15cfa732..26118321d 100644 --- a/ILSpy/NavigationHistory.cs +++ b/ILSpy/NavigationHistory.cs @@ -10,10 +10,10 @@ namespace ICSharpCode.ILSpy /// /// Stores the navigation history. /// - sealed class NavigationHistory + sealed class NavigationHistory { - List back = new List(); - List forward = new List(); + List back = new List(); + List forward = new List(); public bool CanNavigateBack { get { return back.Count > 0; } @@ -23,27 +23,27 @@ namespace ICSharpCode.ILSpy get { return forward.Count > 0; } } - public SharpTreeNode GoBack(SharpTreeNode oldNode) + public T GoBack(T oldNode) { if (oldNode != null) forward.Add(oldNode); - SharpTreeNode node = back[back.Count - 1]; + T node = back[back.Count - 1]; back.RemoveAt(back.Count - 1); return node; } - public SharpTreeNode GoForward(SharpTreeNode oldNode) + public T GoForward(T oldNode) { if (oldNode != null) back.Add(oldNode); - SharpTreeNode node = forward[forward.Count - 1]; + T node = forward[forward.Count - 1]; forward.RemoveAt(forward.Count - 1); return node; } - public void RemoveAll(Predicate predicate) + public void RemoveAll(Predicate predicate) { back.RemoveAll(predicate); forward.RemoveAll(predicate); @@ -55,7 +55,7 @@ namespace ICSharpCode.ILSpy forward.Clear(); } - public void Record(SharpTreeNode node) + public void Record(T node) { forward.Clear(); back.Add(node); diff --git a/ILSpy/TextView/DecompilerTextView.cs b/ILSpy/TextView/DecompilerTextView.cs index 928f11348..6315e8891 100644 --- a/ILSpy/TextView/DecompilerTextView.cs +++ b/ILSpy/TextView/DecompilerTextView.cs @@ -267,9 +267,9 @@ namespace ICSharpCode.ILSpy.TextView /// /// Shows the given output in the text view. /// - void ShowOutput(AvalonEditTextOutput textOutput, IHighlightingDefinition highlighting = null) + void ShowOutput(AvalonEditTextOutput textOutput, IHighlightingDefinition highlighting = null, DecompilerTextViewState state = null) { - Debug.WriteLine("Showing {0} characters of output", textOutput.TextLength); + Debug.WriteLine("Showing {0} characters of output", textOutput.TextLength); Stopwatch w = Stopwatch.StartNew(); textEditor.ScrollToHome(); @@ -287,6 +287,11 @@ namespace ICSharpCode.ILSpy.TextView textEditor.Document = textOutput.GetDocument(); Debug.WriteLine(" Assigning document: {0}", w.Elapsed); w.Restart(); if (textOutput.Foldings.Count > 0) { + if (state != null) { + state.RestoreFoldings(textOutput.Foldings); + textEditor.ScrollToVerticalOffset(state.VerticalOffset); + textEditor.ScrollToHorizontalOffset(state.HorizontalOffset); + } foldingManager = FoldingManager.Install(textEditor.TextArea); foldingManager.UpdateFoldings(textOutput.Foldings.OrderBy(f => f.StartOffset), -1); Debug.WriteLine(" Updating folding: {0}", w.Elapsed); w.Restart(); @@ -349,7 +354,7 @@ namespace ICSharpCode.ILSpy.TextView delegate (Task task) { // handling the result try { AvalonEditTextOutput textOutput = task.Result; - ShowOutput(textOutput, context.Language.SyntaxHighlighting); + ShowOutput(textOutput, context.Language.SyntaxHighlighting, context.Options.TextViewState); } catch (AggregateException aggregateException) { textEditor.SyntaxHighlighting = null; Debug.WriteLine("Decompiler crashed: " + aggregateException.ToString()); @@ -598,5 +603,37 @@ namespace ICSharpCode.ILSpy.TextView return text; } #endregion + + public DecompilerTextViewState GetState() + { + var state = new DecompilerTextViewState(); + if (foldingManager != null) + state.SaveFoldingsState(foldingManager.AllFoldings); + state.VerticalOffset = textEditor.VerticalOffset; + state.HorizontalOffset = textEditor.HorizontalOffset; + return state; + } + } + + public class DecompilerTextViewState + { + private List> ExpandedFoldings; + private int FoldingsChecksum; + public double VerticalOffset; + public double HorizontalOffset; + + public void SaveFoldingsState(IEnumerable foldings) + { + ExpandedFoldings = foldings.Where(f => !f.IsFolded).Select(f => Tuple.Create(f.StartOffset, f.EndOffset)).ToList(); + FoldingsChecksum = foldings.Select(f => f.StartOffset * 3 - f.EndOffset).Aggregate((a, b) => a + b); + } + + internal void RestoreFoldings(List list) + { + var checksum = list.Select(f => f.StartOffset * 3 - f.EndOffset).Aggregate((a, b) => a + b); + if (FoldingsChecksum == checksum) + foreach (var folding in list) + folding.DefaultClosed = !ExpandedFoldings.Any(f => f.Item1 == folding.StartOffset && f.Item2 == folding.EndOffset); + } } }