diff --git a/ILSpy.Tests/Bookmarks/BookmarkNavigationStepTests.cs b/ILSpy.Tests/Bookmarks/BookmarkNavigationStepTests.cs index 0bd495077..84244e7ef 100644 --- a/ILSpy.Tests/Bookmarks/BookmarkNavigationStepTests.cs +++ b/ILSpy.Tests/Bookmarks/BookmarkNavigationStepTests.cs @@ -30,7 +30,7 @@ namespace ICSharpCode.ILSpy.Tests.Bookmarks; [TestFixture] public class BookmarkNavigationStepTests { - // Three bookmarks, the middle one disabled (the reported scenario). + // Three bookmarks with the middle one disabled, so next/previous must step over it. static readonly IReadOnlyList ThreeMiddleDisabled = new[] { true, false, true }; [Test] diff --git a/ILSpy/Bookmarks/BookmarkMargin.cs b/ILSpy/Bookmarks/BookmarkMargin.cs index 038abdd86..eae439db6 100644 --- a/ILSpy/Bookmarks/BookmarkMargin.cs +++ b/ILSpy/Bookmarks/BookmarkMargin.cs @@ -210,7 +210,12 @@ namespace ICSharpCode.ILSpy.Bookmarks { if (bookmark.LineNumber != line) Dispatcher.UIThread.Post(() => bookmark.UpdateRenderedLineNumber(line), DispatcherPriority.Background); - map[line] = bookmark.Enabled ? Images.Bookmark : Images.BookmarkDisable; + // When two bookmarks resolve to the same line, an enabled one wins so the line never + // reads as disabled while an active bookmark sits on it. + if (bookmark.Enabled) + map[line] = Images.Bookmark; + else if (!map.ContainsKey(line)) + map[line] = Images.BookmarkDisable; } } return glyphByLine = map; diff --git a/ILSpy/TextView/DecompilerTextView.axaml.cs b/ILSpy/TextView/DecompilerTextView.axaml.cs index a0878826f..54eb45e70 100644 --- a/ILSpy/TextView/DecompilerTextView.axaml.cs +++ b/ILSpy/TextView/DecompilerTextView.axaml.cs @@ -821,30 +821,9 @@ namespace ICSharpCode.ILSpy.TextView } void RestoreBookmarkFoldings(Bookmarks.BookmarkViewState viewState) - => RestoreCurrentFoldings(viewState.ToDecompilerTextViewState().Foldings); - - void RestoreCurrentFoldings(FoldingsViewState.Snapshot? saved) { - if (saved == null || activeFoldingManager is not { } manager) - return; - - var current = FoldingsViewState.Capture(manager.AllFoldings); - if (current.Checksum != saved.Value.Checksum) - return; - - foreach (var folding in manager.AllFoldings) - { - bool wasExpanded = false; - foreach (var (start, end) in saved.Value.Expanded) - { - if (start == folding.StartOffset && end == folding.EndOffset) - { - wasExpanded = true; - break; - } - } - folding.IsFolded = !wasExpanded; - } + if (viewState.ToDecompilerTextViewState().Foldings is { } saved && activeFoldingManager is { } manager) + FoldingsViewState.Restore(manager.AllFoldings, saved); } // Scrolls so sits in the middle of the viewport. AvaloniaEdit's @@ -888,7 +867,7 @@ namespace ICSharpCode.ILSpy.TextView return null; foreach (var method in debug.Methods) { - if (method.Token == bookmark.Token && method.AssemblyFullName == bookmark.AssemblyFullName + if (MatchesBookmark(method, bookmark) && method.TryGetLineForOffset(bookmark.ILOffset, out var bodyLine)) { if (updateRenderedLine) @@ -912,9 +891,7 @@ namespace ICSharpCode.ILSpy.TextView { foreach (var segment in references) { - if (segment.IsDefinition && segment.Reference is IEntity entity - && (uint)System.Reflection.Metadata.Ecma335.MetadataTokens.GetToken(entity.MetadataToken) == bookmark.Token - && entity.ParentModule?.MetadataFile?.FullName == bookmark.AssemblyFullName) + if (segment.IsDefinition && segment.Reference is IEntity entity && MatchesBookmark(entity, bookmark)) { var line = Editor.Document.GetLineByOffset(segment.StartOffset).LineNumber; if (updateRenderedLine) @@ -926,13 +903,24 @@ namespace ICSharpCode.ILSpy.TextView return null; } + // The token + assembly identity that ties a bookmark to a member in the current document. The + // module identity is checked too, so a token value shared across assemblies can't match the + // wrong member. Both forms (the body-anchor debug map and a reference segment's entity) are + // kept in step here rather than spelled out at each call site. + static bool MatchesBookmark(Bookmarks.MethodDebugInfo method, Bookmarks.Bookmark bookmark) + => method.Token == bookmark.Token && method.AssemblyFullName == bookmark.AssemblyFullName; + + static bool MatchesBookmark(IEntity entity, Bookmarks.Bookmark bookmark) + => (uint)MetadataTokens.GetToken(entity.MetadataToken) == bookmark.Token + && entity.ParentModule?.MetadataFile?.FullName == bookmark.AssemblyFullName; + static bool DocumentContainsBookmarkToken(DecompilerTabPageModel model, Bookmarks.Bookmark bookmark) { if (model.DebugInfo != null) { foreach (var method in model.DebugInfo.Methods) { - if (method.Token == bookmark.Token && method.AssemblyFullName == bookmark.AssemblyFullName) + if (MatchesBookmark(method, bookmark)) return true; } } @@ -941,9 +929,7 @@ namespace ICSharpCode.ILSpy.TextView { foreach (var segment in model.References) { - if (segment.Reference is IEntity entity - && (uint)MetadataTokens.GetToken(entity.MetadataToken) == bookmark.Token - && entity.ParentModule?.MetadataFile?.FullName == bookmark.AssemblyFullName) + if (segment.Reference is IEntity entity && MatchesBookmark(entity, bookmark)) return true; } } diff --git a/ILSpy/TextView/FoldingsViewState.cs b/ILSpy/TextView/FoldingsViewState.cs index 10be9a64f..a1941e536 100644 --- a/ILSpy/TextView/FoldingsViewState.cs +++ b/ILSpy/TextView/FoldingsViewState.cs @@ -88,19 +88,33 @@ namespace ICSharpCode.ILSpy.TextView if (checksum != saved.Checksum) return false; foreach (var folding in newFoldings) + folding.DefaultClosed = !IsExpanded(saved, folding.StartOffset, folding.EndOffset); + return true; + } + + /// + /// Applies to live already installed in a + /// FoldingManager by toggling . Returns true when the + /// layout still matches the snapshot; false (leaving the foldings untouched) on a mismatch. + /// + public static bool Restore(IEnumerable foldings, Snapshot saved) + { + var sections = foldings as IReadOnlyList ?? foldings.ToList(); + if (Capture(sections).Checksum != saved.Checksum) + return false; + foreach (var folding in sections) + folding.IsFolded = !IsExpanded(saved, folding.StartOffset, folding.EndOffset); + return true; + } + + static bool IsExpanded(Snapshot saved, int start, int end) + { + foreach (var (s, e) in saved.Expanded) { - bool wasExpanded = false; - foreach (var (start, end) in saved.Expanded) - { - if (start == folding.StartOffset && end == folding.EndOffset) - { - wasExpanded = true; - break; - } - } - folding.DefaultClosed = !wasExpanded; + if (s == start && e == end) + return true; } - return true; + return false; } } }