Auto size symbol lookup popup

This commit is contained in:
Matt Parker
2025-10-27 18:58:29 +10:00
parent 4e93a7ae6d
commit 308b3f319f
6 changed files with 59 additions and 52 deletions

View File

@@ -667,12 +667,30 @@ public class RoslynAnalysis(ILogger<RoslynAnalysis> logger, BuildService buildSe
return changedFilesWithText;
}
public async Task<ISymbol?> GetEnclosingSymbolForReferenceLocation(ReferenceLocation referenceLocation)
public record IdeReferenceLocationResult(ReferenceLocation ReferenceLocation, SharpIdeFile? File, ISymbol? EnclosingSymbol);
public async Task<IdeReferenceLocationResult?> GetIdeReferenceLocationResult(ReferenceLocation referenceLocation)
{
var semanticModel = await referenceLocation.Document.GetSemanticModelAsync();
if (semanticModel is null) return null;
var enclosingSymbol = ReferenceLocationExtensions.GetEnclosingMethodOrPropertyOrField(semanticModel, referenceLocation);
return enclosingSymbol;
var lineSpan = referenceLocation.Location.GetMappedLineSpan();
var file = _sharpIdeSolutionModel!.AllFiles.SingleOrDefault(f => f.Path == lineSpan.Path);
var result = new IdeReferenceLocationResult(referenceLocation, file!, enclosingSymbol);
return result;
}
public async Task<ImmutableArray<IdeReferenceLocationResult>> GetIdeReferenceLocationResults(ImmutableArray<ReferenceLocation> referenceLocations)
{
var results = new List<IdeReferenceLocationResult>();
foreach (var referenceLocation in referenceLocations)
{
var result = await GetIdeReferenceLocationResult(referenceLocation);
if (result is not null)
{
results.Add(result);
}
}
return results.ToImmutableArray();
}
public async Task<ImmutableArray<ReferencedSymbol>> FindAllSymbolReferences(ISymbol symbol, CancellationToken cancellationToken = default)
@@ -682,7 +700,7 @@ public class RoslynAnalysis(ILogger<RoslynAnalysis> logger, BuildService buildSe
var solution = _workspace!.CurrentSolution;
var references = await SymbolFinder.FindReferencesAsync(symbol, solution, cancellationToken);
return references.AsImmutable();
return references.ToImmutableArray();
}
public async Task<(ISymbol?, LinePositionSpan?, TokenSemanticInfo?)> LookupSymbolSemanticInfo(SharpIdeFile fileModel, LinePosition linePosition)

View File

@@ -48,20 +48,15 @@ public partial class SharpIdeCodeEdit
else
{
// Show popup to select which reference to go to
var scene = _symbolUsagePopupScene.Instantiate<SymbolLookupPopup>();
var locationsAndFiles = locations.Select(s =>
{
var lineSpan = s.Location.GetMappedLineSpan();
var file = Solution!.AllFiles.SingleOrDefault(f => f.Path == lineSpan.Path);
return (s, file);
}).Where(t => t.file is not null).ToImmutableArray();
scene.Locations = locations;
scene.LocationsAndFiles = locationsAndFiles!;
scene.Symbol = semanticInfo.Value.DeclaredSymbol;
var symbolLookupPopup = _symbolUsagePopupScene.Instantiate<SymbolLookupPopup>();
var ideReferenceLocationResults = await _roslynAnalysis.GetIdeReferenceLocationResults(locations);
symbolLookupPopup.IdeReferenceLocationResults = ideReferenceLocationResults;
symbolLookupPopup.Symbol = semanticInfo.Value.DeclaredSymbol;
symbolLookupPopup.Size = new Vector2I(1, 1); // Set tiny size so it autosizes up based on child content
await this.InvokeAsync(() =>
{
AddChild(scene);
scene.PopupCenteredClamped();
AddChild(symbolLookupPopup);
symbolLookupPopup.PopupCentered();
});
}
}

View File

@@ -2,6 +2,7 @@ using System.Collections.Immutable;
using Godot;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.FindSymbols;
using SharpIDE.Application.Features.Analysis;
using SharpIDE.Application.Features.SolutionDiscovery;
namespace SharpIDE.Godot.Features.SymbolLookup;
@@ -10,8 +11,8 @@ public partial class SymbolLookupPopup : PopupPanel
{
private Label _symbolNameLabel = null!;
private VBoxContainer _usagesContainer = null!;
public ImmutableArray<ReferenceLocation> Locations { get; set; }
public ImmutableArray<(ReferenceLocation location, SharpIdeFile file)> LocationsAndFiles { get; set; }
public ImmutableArray<RoslynAnalysis.IdeReferenceLocationResult> IdeReferenceLocationResults { get; set; }
public ISymbol Symbol { get; set; } = null!;
private readonly PackedScene _symbolUsageScene = ResourceLoader.Load<PackedScene>("uid://dokm0dyac2enh");
@@ -24,11 +25,12 @@ public partial class SymbolLookupPopup : PopupPanel
AboutToPopup += OnAboutToPopup;
_usagesContainer.GetChildren().ToList().ForEach(s => s.QueueFree());
foreach (var (location, file) in LocationsAndFiles)
foreach (var result in IdeReferenceLocationResults)
{
var resultNode = _symbolUsageScene.Instantiate<SymbolUsageComponent>();
resultNode.Location = location;
resultNode.File = file;
resultNode.Location = result.ReferenceLocation;
resultNode.File = result.File;
resultNode.EnclosingSymbol = result.EnclosingSymbol;
resultNode.ParentSearchWindow = this;
_usagesContainer.AddChild(resultNode);
}

View File

@@ -14,7 +14,7 @@ shadow_size = 4
[node name="SymbolLookupPopup" type="PopupPanel"]
oversampling_override = 1.0
size = Vector2i(505, 340)
size = Vector2i(564, 340)
visible = true
theme_override_styles/panel = SubResource("StyleBoxFlat_cuaw5")
script = ExtResource("1_f5udm")
@@ -50,15 +50,10 @@ unique_name_in_owner = true
layout_mode = 2
text = "'UseWebApi()'"
[node name="ScrollContainer" type="ScrollContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
follow_focus = true
[node name="UsagesVBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/ScrollContainer"]
[node name="UsagesVBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
[node name="SymbolUsageComponent" parent="MarginContainer/VBoxContainer/ScrollContainer/UsagesVBoxContainer" instance=ExtResource("1_k5g0h")]
[node name="SymbolUsageComponent" parent="MarginContainer/VBoxContainer/UsagesVBoxContainer" instance=ExtResource("1_k5g0h")]
layout_mode = 2

View File

@@ -1,4 +1,5 @@
using Godot;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.FindSymbols;
using SharpIDE.Application.Features.Analysis;
using SharpIDE.Application.Features.SolutionDiscovery;
@@ -14,9 +15,8 @@ public partial class SymbolUsageComponent : MarginContainer
public SymbolLookupPopup ParentSearchWindow { get; set; } = null!;
public ReferenceLocation? Location { get; set; }
public SharpIdeFile File { get; set; } = null!;
[Inject] private readonly RoslynAnalysis _roslynAnalysis = null!;
public ISymbol? EnclosingSymbol { get; set; } = null!;
public SharpIdeFile? File { get; set; } = null!;
public override void _Ready()
{
@@ -32,7 +32,10 @@ public partial class SymbolUsageComponent : MarginContainer
{
var mappedLineSpan = Location!.Value.Location.GetMappedLineSpan();
var fileLinePosition = new SharpIdeFileLinePosition { Line = mappedLineSpan.StartLinePosition.Line, Column = mappedLineSpan.StartLinePosition.Character };
GodotGlobalEvents.Instance.FileExternallySelected.InvokeParallelFireAndForget(File, fileLinePosition);
if (File is not null)
{
GodotGlobalEvents.Instance.FileExternallySelected.InvokeParallelFireAndForget(File, fileLinePosition);
}
ParentSearchWindow.Hide();
}
@@ -41,13 +44,8 @@ public partial class SymbolUsageComponent : MarginContainer
if (result is null) return;
var mappedLineSpan = result.Value.Location.GetMappedLineSpan();
_fileNameLabel.Text = File.Name;
_fileNameLabel.Text = File?.Name ?? Path.GetFileName(mappedLineSpan.Path);
_lineNumberLabel.Text = (mappedLineSpan.StartLinePosition.Line + 1).ToString();
_ = Task.GodotRun(async () =>
{
var enclosingSymbol = await _roslynAnalysis.GetEnclosingSymbolForReferenceLocation(result.Value);
await this.InvokeAsync(() => _enclosingSymbolLabel.Text = enclosingSymbol?.Name);
});
_enclosingSymbolLabel.Text = EnclosingSymbol?.Name;
}
}

View File

@@ -29,33 +29,32 @@ layout_mode = 2
theme_override_styles/normal = SubResource("StyleBoxFlat_6ov2c")
theme_override_styles/focus = SubResource("StyleBoxFlat_dtmd4")
[node name="MarginContainer" type="MarginContainer" parent="Button"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 2
mouse_filter = 0
mouse_behavior_recursive = 1
theme_override_constants/margin_left = 5
theme_override_constants/margin_right = 5
[node name="HBoxContainer" type="HBoxContainer" parent="Button/MarginContainer"]
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer"]
layout_mode = 2
[node name="EnclosingSymbolLabel" type="Label" parent="Button/MarginContainer/HBoxContainer"]
[node name="EnclosingSymbolLabel" type="Label" parent="MarginContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
text = "Containing Symbol Name"
text_overrun_behavior = 3
[node name="FileNameLabel" type="Label" parent="Button/MarginContainer/HBoxContainer"]
[node name="Spacer" type="Control" parent="MarginContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="FileNameLabel" type="Label" parent="MarginContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
theme_override_colors/font_color = Color(1, 1, 1, 0.7411765)
text = "FileName.cs"
[node name="LineNumberLabel" type="Label" parent="Button/MarginContainer/HBoxContainer"]
[node name="LineNumberLabel" type="Label" parent="MarginContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
theme_override_colors/font_color = Color(1, 1, 1, 0.7411765)