♻️ Improve Problem diagnostics display

This commit is contained in:
Matt Parker
2025-11-26 18:13:37 +10:00
parent 36683406de
commit 84245d888f
2 changed files with 98 additions and 12 deletions

View File

@@ -28,6 +28,7 @@ public partial class ProblemsPanel : Control
public override void _Ready()
{
_diagnosticCustomDrawCallable = new Callable(this, MethodName.DiagnosticCustomDraw);
_tree = GetNode<Tree>("%Tree");
_tree.ItemActivated += TreeOnItemActivated;
_rootItem = _tree.CreateItem();
@@ -78,19 +79,101 @@ public partial class ProblemsPanel : Control
});
}
private Callable? _diagnosticCustomDrawCallable;
private TextLine _diagnosticTextLine = new TextLine(); // Reusing this is based on the assumption that it is called by godot in a single-threaded fashion
private void DiagnosticCustomDraw(TreeItem treeItem, Rect2 rect)
{
var hovered = _tree.GetItemAtPosition(_tree.GetLocalMousePosition()) == treeItem;
var isSelected = treeItem.IsSelected(0);
var diagnosticContainer = treeItem.GetMetadata(0).As<RefCountedContainer<SharpIdeDiagnostic>?>();
if (diagnosticContainer is null) return;
var diagnostic = diagnosticContainer.Item;
var message = diagnostic.Diagnostic.GetMessage();
var severity = diagnostic.Diagnostic.Severity;
var linePosition = diagnostic.Span.Start;
// Get icon based on severity
var icon = severity switch
{
DiagnosticSeverity.Error => ErrorIcon,
DiagnosticSeverity.Warning => WarningIcon,
_ => null
};
// Define padding and spacing
const float padding = 4.0f;
const float iconSize = 22.0f;
const float spacing = 6.0f;
var currentX = rect.Position.X + padding;
var currentY = rect.Position.Y;
// Draw icon
if (icon is not null)
{
var iconRect = new Rect2(currentX, currentY + (rect.Size.Y - iconSize) / 2, iconSize, iconSize);
_tree.DrawTextureRect(icon, iconRect, false);
currentX += iconSize + spacing;
}
// Get font and prepare text
var font = _tree.GetThemeFont("font");
var fontSize = _tree.GetThemeFontSize("font_size");
var textColor = (isSelected, hovered) switch
{
(true, true) => _tree.GetThemeColor("font_hovered_selected_color"),
(true, false) => _tree.GetThemeColor("font_selected_color"),
(false, true) => _tree.GetThemeColor("font_hovered_color"),
(false, false) => _tree.GetThemeColor("font_color")
};
var textYPos = currentY + (rect.Size.Y + fontSize) / 2 - 2;
// Calculate right-hand text widths first
var fileName = Path.GetFileName(diagnostic.FilePath);
var fileNameWidth = font.GetStringSize(fileName, HorizontalAlignment.Left, -1, fontSize).X;
var locationText = $"({linePosition.Line + 1}:{linePosition.Character + 1})";
var locationWidth = font.GetStringSize(locationText, HorizontalAlignment.Left, -1, fontSize).X;
var rightSideWidth = locationWidth + spacing + fileNameWidth + padding;
var textLine = _diagnosticTextLine;
textLine.TextOverrunBehavior = TextServer.OverrunBehavior.TrimEllipsis;
textLine.SetHorizontalAlignment(HorizontalAlignment.Left);
textLine.AddString(message, font, fontSize);
// Draw message with width constraint to avoid overlap
var maxMessageWidth = rect.Size.X - currentX - rightSideWidth - spacing;
if (maxMessageWidth > 0)
{
textLine.Width = maxMessageWidth;
textLine.Draw(_tree.GetCanvasItem(), new Vector2(currentX, textYPos - textLine.GetLineAscent()), textColor);
textLine.Clear();
//_tree.DrawString(font, new Vector2(currentX, textYPos), message, HorizontalAlignment.Left, maxMessageWidth, fontSize, textColor);
}
// Draw location info (line:column) on the right side
var locationX = rect.Position.X + rect.Size.X - rightSideWidth;
var locationColor = textColor with { A = 0.5f };
_tree.DrawString(font, new Vector2(locationX, textYPos), locationText, HorizontalAlignment.Left, -1, fontSize, locationColor);
// Draw file name on the right side, after the location
var fileNameX = locationX + locationWidth + spacing;
var fileNameColor = textColor with { A = 0.7f };
_tree.DrawString(font, new Vector2(fileNameX, textYPos), fileName, HorizontalAlignment.Left, -1, fontSize, fileNameColor);
}
private async Task CreateDiagnosticTreeItem(Tree tree, TreeItem parent, ViewChangedEvent<SharpIdeDiagnostic, TreeItemContainer> e)
{
await this.InvokeAsync(() =>
{
var diagItem = tree.CreateItem(parent);
diagItem.SetText(0, e.NewItem.Value.Diagnostic.GetMessage());
diagItem.SetCellMode(0, TreeItem.TreeCellMode.Custom);
diagItem.SetCustomAsButton(0, true);
diagItem.SetTooltipText(0, e.NewItem.Value.Diagnostic.GetMessage());
diagItem.SetMetadata(0, new RefCountedContainer<SharpIdeDiagnostic>(e.NewItem.Value));
diagItem.SetIcon(0, e.NewItem.Value.Diagnostic.Severity switch
{
DiagnosticSeverity.Error => ErrorIcon,
DiagnosticSeverity.Warning => WarningIcon,
_ => null
});
// Avoid allocation via Callable.From((TreeItem s, Rect2 x) => CustomDraw(s, x))
diagItem.SetCustomDrawCallback(0, _diagnosticCustomDrawCallable!.Value);
e.NewItem.View.Value = diagItem;
});
}

View File

@@ -6,6 +6,10 @@
[ext_resource type="Texture2D" uid="uid://cqt30ma6xgder" path="res://Features/SolutionExplorer/Resources/Csproj.svg" id="4_wqdox"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_wqdox"]
content_margin_left = 5.0
content_margin_top = 5.0
content_margin_right = 5.0
content_margin_bottom = 5.0
[node name="ProblemsPanel" type="Control"]
layout_mode = 3
@@ -42,15 +46,14 @@ text = "Problems"
[node name="HSeparator" type="HSeparator" parent="VBoxContainer"]
layout_mode = 2
[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
[node name="Tree" type="Tree" parent="VBoxContainer/ScrollContainer"]
[node name="Tree" type="Tree" parent="VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/inner_item_margin_left = 5
theme_override_constants/inner_item_margin_right = 5
theme_override_constants/draw_guides = 0
theme_override_styles/panel = SubResource("StyleBoxEmpty_wqdox")
hide_root = true
scroll_horizontal_enabled = false