mirror of https://github.com/icsharpcode/ILSpy.git
Browse Source
Bookmarks now capture and restore the requested decompiler view state, support rendered-line fallback anchors, expose hover and pane location affordances, route bookmark context-menu actions through the clicked offset, and update the JSON sidecar under a mutex so concurrent instances do not overwrite unrelated bookmark edits. The plan file records the completed checklist and the remaining manual smoke-test gap. Assisted-by: CodeAlta:gpt-5.5:CodeAltapull/3839/head
23 changed files with 1146 additions and 219 deletions
@ -0,0 +1,138 @@ |
|||||||
|
// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||||
|
// software and associated documentation files (the "Software"), to deal in the Software
|
||||||
|
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||||
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||||
|
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all copies or
|
||||||
|
// substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||||
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
// DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
using System.Linq; |
||||||
|
using System.Reflection; |
||||||
|
using System.Threading.Tasks; |
||||||
|
|
||||||
|
using Avalonia.Headless.NUnit; |
||||||
|
using Avalonia.Threading; |
||||||
|
|
||||||
|
using AvaloniaEdit.Document; |
||||||
|
|
||||||
|
using AwesomeAssertions; |
||||||
|
|
||||||
|
using ICSharpCode.ILSpy; |
||||||
|
using ICSharpCode.ILSpy.AppEnv; |
||||||
|
using ICSharpCode.ILSpy.Bookmarks; |
||||||
|
using ICSharpCode.ILSpy.Properties; |
||||||
|
using ICSharpCode.ILSpy.TextView; |
||||||
|
using ICSharpCode.ILSpy.TreeNodes; |
||||||
|
|
||||||
|
using NUnit.Framework; |
||||||
|
|
||||||
|
namespace ICSharpCode.ILSpy.Tests.Bookmarks; |
||||||
|
|
||||||
|
[TestFixture] |
||||||
|
public class BookmarkContextMenuTests |
||||||
|
{ |
||||||
|
[AvaloniaTest] |
||||||
|
public async Task Toggle_Bookmark_Entry_Acts_On_The_Right_Clicked_Line_Not_The_Caret() |
||||||
|
{ |
||||||
|
var (window, vm) = await TestHarness.BootAsync(3); |
||||||
|
var manager = AppComposition.Current.GetExport<BookmarkManager>(); |
||||||
|
manager.Clear(); |
||||||
|
|
||||||
|
var coreLibName = typeof(object).Assembly.GetName().Name!; |
||||||
|
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(coreLibName, "System", "System.Object"); |
||||||
|
vm.AssemblyTreeModel.SelectNode(typeNode); |
||||||
|
await vm.DockWorkspace.WaitForDecompiledTextAsync(); |
||||||
|
|
||||||
|
var view = await window.WaitForComponent<DecompilerTextView>(); |
||||||
|
for (int i = 0; i < 8; i++) |
||||||
|
{ |
||||||
|
Dispatcher.UIThread.RunJobs(); |
||||||
|
await Task.Delay(25); |
||||||
|
} |
||||||
|
|
||||||
|
var bookmarkableLines = Enumerable.Range(1, view.Editor.Document.LineCount) |
||||||
|
.Where(view.CanToggleBookmarkAtLine) |
||||||
|
.Take(2) |
||||||
|
.ToArray(); |
||||||
|
bookmarkableLines.Should().HaveCount(2, "two distinct bookmarkable lines are needed to tell the caret from the click"); |
||||||
|
|
||||||
|
int caretLine = bookmarkableLines[0]; |
||||||
|
int clickedLine = bookmarkableLines[1]; |
||||||
|
view.Editor.TextArea.Caret.Offset = view.Editor.Document.GetLineByNumber(caretLine).Offset; |
||||||
|
int clickedOffset = view.Editor.Document.GetLineByNumber(clickedLine).Offset; |
||||||
|
|
||||||
|
var toggle = AppComposition.Current.GetExport<ContextMenuEntryRegistry>() |
||||||
|
.GetEntry(nameof(Resources.BookmarkToggle)); |
||||||
|
toggle.Execute(new TextViewContext { TextView = view, TextLocation = clickedOffset }); |
||||||
|
Dispatcher.UIThread.RunJobs(); |
||||||
|
|
||||||
|
manager.Bookmarks.Should().ContainSingle(); |
||||||
|
view.GetLineForBookmark(manager.Bookmarks[0]).Should().Be(clickedLine); |
||||||
|
manager.Bookmarks[0].LocationNodeName.Should().Be("System.Object"); |
||||||
|
} |
||||||
|
|
||||||
|
[AvaloniaTest] |
||||||
|
public async Task Navigation_Does_Not_Fall_Back_To_Entity_When_Saved_Tree_Path_Is_Missing() |
||||||
|
{ |
||||||
|
var (window, vm) = await TestHarness.BootAsync(3); |
||||||
|
var manager = AppComposition.Current.GetExport<BookmarkManager>(); |
||||||
|
manager.Clear(); |
||||||
|
|
||||||
|
var coreLibName = typeof(object).Assembly.GetName().Name!; |
||||||
|
var objectNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(coreLibName, "System", "System.Object"); |
||||||
|
vm.AssemblyTreeModel.SelectNode(objectNode); |
||||||
|
await vm.DockWorkspace.WaitForDecompiledTextAsync(); |
||||||
|
|
||||||
|
var view = await window.WaitForComponent<DecompilerTextView>(); |
||||||
|
for (int i = 0; i < 8; i++) |
||||||
|
{ |
||||||
|
Dispatcher.UIThread.RunJobs(); |
||||||
|
await Task.Delay(25); |
||||||
|
} |
||||||
|
|
||||||
|
int line = Enumerable.Range(1, view.Editor.Document.LineCount) |
||||||
|
.First(view.CanToggleBookmarkAtLine); |
||||||
|
int offset = view.Editor.Document.GetLineByNumber(line).Offset; |
||||||
|
AppComposition.Current.GetExport<ContextMenuEntryRegistry>() |
||||||
|
.GetEntry(nameof(Resources.BookmarkToggle)) |
||||||
|
.Execute(new TextViewContext { TextView = view, TextLocation = offset }); |
||||||
|
Dispatcher.UIThread.RunJobs(); |
||||||
|
|
||||||
|
manager.Bookmarks.Should().ContainSingle(); |
||||||
|
var bookmark = manager.Bookmarks[0]; |
||||||
|
bookmark.ViewState.Should().NotBeNull(); |
||||||
|
bookmark.ViewState = bookmark.ViewState! with { SelectedTreeNodePath = new[] { "Missing assembly", "Missing type" } }; |
||||||
|
|
||||||
|
var stringNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(coreLibName, "System", "System.String"); |
||||||
|
vm.AssemblyTreeModel.SelectNode(stringNode); |
||||||
|
|
||||||
|
await AppComposition.Current.GetExport<BookmarkNavigator>().NavigateToAsync(bookmark); |
||||||
|
|
||||||
|
((object?)vm.AssemblyTreeModel.SelectedItem).Should().BeSameAs(stringNode); |
||||||
|
} |
||||||
|
|
||||||
|
[AvaloniaTest] |
||||||
|
public async Task Queued_Bookmark_Scroll_Ignores_A_Replaced_Document() |
||||||
|
{ |
||||||
|
var (window, _) = await TestHarness.BootAsync(3); |
||||||
|
var view = await window.WaitForComponent<DecompilerTextView>(); |
||||||
|
view.Editor.Document = new TextDocument(string.Join("\n", Enumerable.Range(1, 30).Select(i => $"line {i}"))); |
||||||
|
|
||||||
|
var scrollToLine = typeof(DecompilerTextView).GetMethod("ScrollToLine", BindingFlags.Instance | BindingFlags.NonPublic); |
||||||
|
scrollToLine.Should().NotBeNull(); |
||||||
|
scrollToLine!.Invoke(view, new object?[] { 20, null }); |
||||||
|
|
||||||
|
view.Editor.Document = new TextDocument("replacement"); |
||||||
|
Dispatcher.UIThread.RunJobs(); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,137 @@ |
|||||||
|
// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||||
|
// software and associated documentation files (the "Software"), to deal in the Software
|
||||||
|
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||||
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||||
|
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all copies or
|
||||||
|
// substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||||
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
// DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
using System.Linq; |
||||||
|
using System.Threading.Tasks; |
||||||
|
|
||||||
|
using Avalonia; |
||||||
|
using Avalonia.Headless; |
||||||
|
using Avalonia.Headless.NUnit; |
||||||
|
using Avalonia.Input; |
||||||
|
using Avalonia.Interactivity; |
||||||
|
using Avalonia.Threading; |
||||||
|
|
||||||
|
using AvaloniaEdit.Rendering; |
||||||
|
|
||||||
|
using AwesomeAssertions; |
||||||
|
|
||||||
|
using ICSharpCode.ILSpy.AppEnv; |
||||||
|
using ICSharpCode.ILSpy.Bookmarks; |
||||||
|
using ICSharpCode.ILSpy.TextView; |
||||||
|
using ICSharpCode.ILSpy.TreeNodes; |
||||||
|
|
||||||
|
using NUnit.Framework; |
||||||
|
|
||||||
|
namespace ICSharpCode.ILSpy.Tests.Bookmarks; |
||||||
|
|
||||||
|
[TestFixture] |
||||||
|
public class BookmarkGutterTests |
||||||
|
{ |
||||||
|
[AvaloniaTest] |
||||||
|
public async Task Clicking_Bookmark_Gutter_Toggles_The_Visible_Line() |
||||||
|
{ |
||||||
|
var (window, vm) = await TestHarness.BootAsync(3); |
||||||
|
var manager = AppComposition.Current.GetExport<BookmarkManager>(); |
||||||
|
manager.Clear(); |
||||||
|
|
||||||
|
var coreLibName = typeof(object).Assembly.GetName().Name!; |
||||||
|
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(coreLibName, "System", "System.Object"); |
||||||
|
vm.AssemblyTreeModel.SelectNode(typeNode); |
||||||
|
await vm.DockWorkspace.WaitForDecompiledTextAsync(); |
||||||
|
|
||||||
|
var view = await window.WaitForComponent<DecompilerTextView>(); |
||||||
|
for (int i = 0; i < 8; i++) |
||||||
|
{ |
||||||
|
Dispatcher.UIThread.RunJobs(); |
||||||
|
await Task.Delay(25); |
||||||
|
} |
||||||
|
window.UpdateLayout(); |
||||||
|
|
||||||
|
var margin = view.Editor.TextArea.LeftMargins.OfType<BookmarkMargin>().Single(); |
||||||
|
margin.Bounds.Width.Should().BeGreaterThan(0, "the bookmark gutter must reserve hit-testable space"); |
||||||
|
|
||||||
|
var textView = view.Editor.TextArea.TextView; |
||||||
|
textView.EnsureVisualLines(); |
||||||
|
var visualLine = textView.VisualLines.First(line => view.CanToggleBookmarkAtLine(line.FirstDocumentLine.LineNumber)); |
||||||
|
int documentLine = visualLine.FirstDocumentLine.LineNumber; |
||||||
|
double y = visualLine.GetTextLineVisualYPosition(visualLine.TextLines[0], VisualYPosition.LineMiddle) - textView.VerticalOffset; |
||||||
|
y.Should().BeInRange(0, margin.Bounds.Height, "the selected visual line must be inside the gutter bounds"); |
||||||
|
var point = margin.TranslatePoint(new Point(margin.Bounds.Width / 2, y), window); |
||||||
|
point.Should().NotBeNull("the gutter line coordinate must map into the test window"); |
||||||
|
|
||||||
|
int pressed = 0; |
||||||
|
margin.AddHandler(InputElement.PointerPressedEvent, (_, _) => pressed++, RoutingStrategies.Tunnel | RoutingStrategies.Bubble); |
||||||
|
|
||||||
|
window.MouseDown(point!.Value, MouseButton.Left); |
||||||
|
window.MouseUp(point.Value, MouseButton.Left); |
||||||
|
Dispatcher.UIThread.RunJobs(); |
||||||
|
|
||||||
|
pressed.Should().BeGreaterThan(0, "the real pointer event must hit the bookmark gutter"); |
||||||
|
manager.Bookmarks.Should().ContainSingle(); |
||||||
|
view.GetLineForBookmark(manager.Bookmarks[0]).Should().Be(documentLine); |
||||||
|
|
||||||
|
window.MouseDown(point.Value, MouseButton.Left); |
||||||
|
window.MouseUp(point.Value, MouseButton.Left); |
||||||
|
Dispatcher.UIThread.RunJobs(); |
||||||
|
|
||||||
|
manager.Bookmarks.Should().BeEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
[AvaloniaTest] |
||||||
|
public async Task RightClicking_Bookmark_Gutter_Does_Not_Toggle() |
||||||
|
{ |
||||||
|
var (window, vm) = await TestHarness.BootAsync(3); |
||||||
|
var manager = AppComposition.Current.GetExport<BookmarkManager>(); |
||||||
|
manager.Clear(); |
||||||
|
|
||||||
|
var coreLibName = typeof(object).Assembly.GetName().Name!; |
||||||
|
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(coreLibName, "System", "System.Object"); |
||||||
|
vm.AssemblyTreeModel.SelectNode(typeNode); |
||||||
|
await vm.DockWorkspace.WaitForDecompiledTextAsync(); |
||||||
|
|
||||||
|
var view = await window.WaitForComponent<DecompilerTextView>(); |
||||||
|
for (int i = 0; i < 8; i++) |
||||||
|
{ |
||||||
|
Dispatcher.UIThread.RunJobs(); |
||||||
|
await Task.Delay(25); |
||||||
|
} |
||||||
|
window.UpdateLayout(); |
||||||
|
|
||||||
|
var margin = view.Editor.TextArea.LeftMargins.OfType<BookmarkMargin>().Single(); |
||||||
|
var textView = view.Editor.TextArea.TextView; |
||||||
|
textView.EnsureVisualLines(); |
||||||
|
var visualLine = textView.VisualLines.First(line => view.CanToggleBookmarkAtLine(line.FirstDocumentLine.LineNumber)); |
||||||
|
double y = visualLine.GetTextLineVisualYPosition(visualLine.TextLines[0], VisualYPosition.LineMiddle) - textView.VerticalOffset; |
||||||
|
var point = margin.TranslatePoint(new Point(margin.Bounds.Width / 2, y), window); |
||||||
|
point.Should().NotBeNull("the gutter line coordinate must map into the test window"); |
||||||
|
|
||||||
|
window.MouseDown(point!.Value, MouseButton.Right); |
||||||
|
window.MouseUp(point.Value, MouseButton.Right); |
||||||
|
Dispatcher.UIThread.RunJobs(); |
||||||
|
|
||||||
|
manager.Bookmarks.Should().BeEmpty("a right-click in the gutter must not toggle a bookmark"); |
||||||
|
|
||||||
|
// The same coordinate still toggles on a left-click, proving the gesture is button-gated
|
||||||
|
// rather than disabled at that line.
|
||||||
|
window.MouseDown(point.Value, MouseButton.Left); |
||||||
|
window.MouseUp(point.Value, MouseButton.Left); |
||||||
|
Dispatcher.UIThread.RunJobs(); |
||||||
|
|
||||||
|
manager.Bookmarks.Should().ContainSingle("a left-click at the same coordinate still toggles"); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,79 @@ |
|||||||
|
// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||||
|
// software and associated documentation files (the "Software"), to deal in the Software
|
||||||
|
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||||
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||||
|
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all copies or
|
||||||
|
// substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||||
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
// DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
using System.Linq; |
||||||
|
|
||||||
|
using Avalonia.Controls; |
||||||
|
using Avalonia.Headless.NUnit; |
||||||
|
using Avalonia.Threading; |
||||||
|
|
||||||
|
using AwesomeAssertions; |
||||||
|
|
||||||
|
using ICSharpCode.ILSpy.Bookmarks; |
||||||
|
using ICSharpCode.ILSpy.Properties; |
||||||
|
using ICSharpCode.ILSpy.Views.Controls; |
||||||
|
|
||||||
|
using NUnit.Framework; |
||||||
|
|
||||||
|
namespace ICSharpCode.ILSpy.Tests.Bookmarks; |
||||||
|
|
||||||
|
[TestFixture] |
||||||
|
public class BookmarksPaneStructureTests |
||||||
|
{ |
||||||
|
[AvaloniaTest] |
||||||
|
public void Toolbar_uses_main_toolbar_chrome_and_button_content() |
||||||
|
{ |
||||||
|
var pane = new BookmarksPane(); |
||||||
|
var toolbarBorder = pane.FindControl<Border>("ToolbarBorder")!; |
||||||
|
var toolbarRoot = pane.FindControl<StackPanel>("ToolbarRoot")!; |
||||||
|
|
||||||
|
toolbarBorder.BorderThickness.Should().Be(new Avalonia.Thickness(0, 0, 0, 1)); |
||||||
|
toolbarBorder.MinHeight.Should().Be(29); |
||||||
|
toolbarBorder.Padding.Should().Be(new Avalonia.Thickness(3)); |
||||||
|
toolbarRoot.Children.OfType<Separator>().Should().HaveCount(2); |
||||||
|
toolbarRoot.Children.OfType<Button>().Should().AllSatisfy(button => { |
||||||
|
button.Content.Should().BeOfType<GrayscaleAwareImage>(); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
[AvaloniaTest] |
||||||
|
public void Module_column_shows_module_name_with_full_path_tooltip() |
||||||
|
{ |
||||||
|
var pane = new BookmarksPane(); |
||||||
|
var grid = pane.FindControl<DataGrid>("BookmarkGrid")!; |
||||||
|
var column = grid.Columns.OfType<DataGridTemplateColumn>() |
||||||
|
.Single(c => Equals(c.Header, Resources.BookmarkModule)); |
||||||
|
var bookmark = new Bookmark { |
||||||
|
Name = "Bookmark0", |
||||||
|
FileName = @"C:\assemblies\Sample.dll", |
||||||
|
AssemblyFullName = "Sample, Version=1.0.0.0", |
||||||
|
ModuleName = "Sample.dll", |
||||||
|
Token = 0x02000001, |
||||||
|
Kind = BookmarkKind.Token, |
||||||
|
LineNumber = 1, |
||||||
|
MemberName = "Sample.Type", |
||||||
|
}; |
||||||
|
|
||||||
|
var textBlock = column.CellTemplate!.Build(bookmark).Should().BeOfType<TextBlock>().Subject; |
||||||
|
textBlock.DataContext = bookmark; |
||||||
|
Dispatcher.UIThread.RunJobs(); |
||||||
|
|
||||||
|
textBlock.Text.Should().Be("Sample.dll"); |
||||||
|
ToolTip.GetTip(textBlock).Should().Be(@"C:\assemblies\Sample.dll"); |
||||||
|
} |
||||||
|
} |
||||||
|
Before Width: | Height: | Size: 912 B |
|
Before Width: | Height: | Size: 1008 B |
|
Before Width: | Height: | Size: 1.6 KiB |
@ -0,0 +1,100 @@ |
|||||||
|
// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||||
|
// software and associated documentation files (the "Software"), to deal in the Software
|
||||||
|
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||||
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||||
|
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all copies or
|
||||||
|
// substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||||
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
// DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Linq; |
||||||
|
|
||||||
|
using ICSharpCode.ILSpy.Options; |
||||||
|
using ICSharpCode.ILSpy.TextView; |
||||||
|
|
||||||
|
namespace ICSharpCode.ILSpy.Bookmarks |
||||||
|
{ |
||||||
|
/// <summary>A serializable folding range stored inside a bookmark view-state payload.</summary>
|
||||||
|
public sealed record BookmarkFoldingRange(int Start, int End); |
||||||
|
|
||||||
|
/// <summary>Display settings that affect the rendered C# text layout.</summary>
|
||||||
|
public sealed record BookmarkRenderedLayoutSettings( |
||||||
|
bool FoldBraces, |
||||||
|
bool ExpandMemberDefinitions, |
||||||
|
bool ExpandUsingDeclarations, |
||||||
|
bool ShowDebugInfo, |
||||||
|
bool IndentationUseTabs, |
||||||
|
int IndentationSize, |
||||||
|
int IndentationTabSize) |
||||||
|
{ |
||||||
|
public static BookmarkRenderedLayoutSettings From(DisplaySettings display) |
||||||
|
=> new( |
||||||
|
display.FoldBraces, |
||||||
|
display.ExpandMemberDefinitions, |
||||||
|
display.ExpandUsingDeclarations, |
||||||
|
display.ShowDebugInfo, |
||||||
|
display.IndentationUseTabs, |
||||||
|
display.IndentationSize, |
||||||
|
display.IndentationTabSize); |
||||||
|
|
||||||
|
public void ApplyTo(DisplaySettings display) |
||||||
|
{ |
||||||
|
display.FoldBraces = FoldBraces; |
||||||
|
display.ExpandMemberDefinitions = ExpandMemberDefinitions; |
||||||
|
display.ExpandUsingDeclarations = ExpandUsingDeclarations; |
||||||
|
display.ShowDebugInfo = ShowDebugInfo; |
||||||
|
display.IndentationUseTabs = IndentationUseTabs; |
||||||
|
display.IndentationSize = IndentationSize; |
||||||
|
display.IndentationTabSize = IndentationTabSize; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Serializable editor view state captured with a bookmark. The version is part of the payload so
|
||||||
|
/// future schema changes can be handled without changing the bookmark file version.
|
||||||
|
/// </summary>
|
||||||
|
public sealed record BookmarkViewState( |
||||||
|
int Version, |
||||||
|
int CaretOffset, |
||||||
|
double VerticalOffset, |
||||||
|
double HorizontalOffset, |
||||||
|
int? FoldingChecksum, |
||||||
|
IReadOnlyList<BookmarkFoldingRange>? ExpandedFoldings, |
||||||
|
BookmarkRenderedLayoutSettings? RenderedLayoutSettings = null, |
||||||
|
IReadOnlyList<string>? SelectedTreeNodePath = null) |
||||||
|
{ |
||||||
|
public static BookmarkViewState From(DecompilerTextViewState state, DisplaySettings? displaySettings = null, |
||||||
|
IReadOnlyList<string>? selectedTreeNodePath = null) |
||||||
|
=> new( |
||||||
|
1, |
||||||
|
state.CaretOffset, |
||||||
|
state.VerticalOffset, |
||||||
|
state.HorizontalOffset, |
||||||
|
state.Foldings?.Checksum, |
||||||
|
state.Foldings?.Expanded.Select(r => new BookmarkFoldingRange(r.Start, r.End)).ToArray(), |
||||||
|
displaySettings != null ? BookmarkRenderedLayoutSettings.From(displaySettings) : null, |
||||||
|
selectedTreeNodePath); |
||||||
|
|
||||||
|
public DecompilerTextViewState ToDecompilerTextViewState() |
||||||
|
{ |
||||||
|
FoldingsViewState.Snapshot? foldings = null; |
||||||
|
if (FoldingChecksum is { } checksum) |
||||||
|
{ |
||||||
|
var expanded = ExpandedFoldings?.Select(r => (r.Start, r.End)).ToArray() |
||||||
|
?? System.Array.Empty<(int Start, int End)>(); |
||||||
|
foldings = new FoldingsViewState.Snapshot(expanded, checksum); |
||||||
|
} |
||||||
|
return new DecompilerTextViewState(CaretOffset, VerticalOffset, HorizontalOffset, foldings); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue