diff --git a/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit.cs b/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit.cs index e8abdcb..7de7b97 100644 --- a/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit.cs +++ b/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit.cs @@ -244,136 +244,7 @@ public partial class SharpIdeCodeEdit : CodeEdit //SetSymbolLookupWordAsValid(valid); SetSymbolLookupWordAsValid(true); } - - private void CloseSymbolHoverWindow() - { - _symbolHoverTimer?.EmitSignal(Timer.SignalName.Timeout); - _symbolHoverTimer = null; - } - - private Timer? _symbolHoverTimer = null!; - // This method is a bit of a disaster - we create an additional invisible Window, so that the tooltip window doesn't disappear while the mouse is over the hovered symbol - private async void OnSymbolHovered(string symbol, long line, long column) - { - if (HasFocus() is false) return; // only show if we have focus, every tab is currently listening for this event, maybe find a better way - var globalMousePosition = GetGlobalMousePosition(); // don't breakpoint before this, else your mouse position will be wrong - var lineHeight = GetLineHeight(); - GD.Print($"Symbol hovered: {symbol} at line {line}, column {column}"); - - var (roslynSymbol, linePositionSpan) = await _roslynAnalysis.LookupSymbol(_currentFile, new LinePosition((int)line, (int)column)); - if (roslynSymbol is null || linePositionSpan is null) - { - return; - } - - var symbolNameHoverWindow = new Window(); - symbolNameHoverWindow.WrapControls = true; - symbolNameHoverWindow.Unresizable = true; - symbolNameHoverWindow.Transparent = true; - symbolNameHoverWindow.Borderless = true; - symbolNameHoverWindow.PopupWMHint = true; - symbolNameHoverWindow.PopupWindow = true; - symbolNameHoverWindow.MinimizeDisabled = true; - symbolNameHoverWindow.MaximizeDisabled = true; - symbolNameHoverWindow.Exclusive = false; - symbolNameHoverWindow.Transient = true; - symbolNameHoverWindow.TransientToFocused = true; - symbolNameHoverWindow.Unfocusable = true; - // To debug location, make type a PopupPanel, and uncomment - //symbolNameHoverWindow.AddThemeStyleboxOverride("panel", new StyleBoxFlat { BgColor = new Color(1, 0, 0, 0.5f) }); - - var startSymbolCharRect = GetRectAtLineColumn(linePositionSpan.Value.Start.Line, linePositionSpan.Value.Start.Character + 1); - var endSymbolCharRect = GetRectAtLineColumn(linePositionSpan.Value.End.Line, linePositionSpan.Value.End.Character); - symbolNameHoverWindow.Size = new Vector2I(endSymbolCharRect.End.X - startSymbolCharRect.Position.X, lineHeight); - - var globalPosition = GetGlobalPosition(); - var startSymbolCharGlobalPos = startSymbolCharRect.Position + globalPosition; - var endSymbolCharGlobalPos = endSymbolCharRect.Position + globalPosition; - - AddChild(symbolNameHoverWindow); - symbolNameHoverWindow.Position = new Vector2I((int)startSymbolCharGlobalPos.X, (int)endSymbolCharGlobalPos.Y); - symbolNameHoverWindow.Popup(); - - var tooltipWindow = new Window(); - tooltipWindow.WrapControls = true; - tooltipWindow.Unresizable = true; - tooltipWindow.Transparent = true; - tooltipWindow.Borderless = true; - tooltipWindow.PopupWMHint = true; - tooltipWindow.PopupWindow = true; - tooltipWindow.MinimizeDisabled = true; - tooltipWindow.MaximizeDisabled = true; - tooltipWindow.Exclusive = false; - tooltipWindow.Transient = true; - tooltipWindow.TransientToFocused = true; - tooltipWindow.Unfocusable = true; - - var timer = new Timer { WaitTime = 0.05f, OneShot = true, Autostart = false }; - tooltipWindow.AddChild(timer); - timer.Timeout += () => - { - tooltipWindow.QueueFree(); - symbolNameHoverWindow.QueueFree(); - }; - _symbolHoverTimer = timer; - tooltipWindow.MouseExited += () => timer.Start(); - tooltipWindow.MouseEntered += () => timer.Stop(); - symbolNameHoverWindow.MouseExited += () => timer.Start(); - symbolNameHoverWindow.MouseEntered += () => timer.Stop(); - - var styleBox = new StyleBoxFlat - { - BgColor = new Color("2b2d30"), - BorderColor = new Color("3e4045"), - BorderWidthTop = 1, - BorderWidthBottom = 1, - BorderWidthLeft = 1, - BorderWidthRight = 1, - CornerRadiusBottomLeft = 4, - CornerRadiusBottomRight = 4, - CornerRadiusTopLeft = 4, - CornerRadiusTopRight = 4, - ShadowSize = 2, - ShadowColor = new Color(0, 0, 0, 0.5f), - ExpandMarginTop = -2, // negative margin seems to fix shadow being cut off? - ExpandMarginBottom = -2, - ExpandMarginLeft = -2, - ExpandMarginRight = -2, - ContentMarginTop = 10, - ContentMarginBottom = 10, - ContentMarginLeft = 12, - ContentMarginRight = 12 - }; - var panel = new PanelContainer(); - panel.AddThemeStyleboxOverride("panel", styleBox); - - var symbolInfoNode = roslynSymbol switch - { - IMethodSymbol methodSymbol => SymbolInfoComponents.GetMethodSymbolInfo(methodSymbol), - INamedTypeSymbol namedTypeSymbol => SymbolInfoComponents.GetNamedTypeSymbolInfo(namedTypeSymbol), - IPropertySymbol propertySymbol => SymbolInfoComponents.GetPropertySymbolInfo(propertySymbol), - IFieldSymbol fieldSymbol => SymbolInfoComponents.GetFieldSymbolInfo(fieldSymbol), - IParameterSymbol parameterSymbol => SymbolInfoComponents.GetParameterSymbolInfo(parameterSymbol), - ILocalSymbol localSymbol => SymbolInfoComponents.GetLocalVariableSymbolInfo(localSymbol), - _ => SymbolInfoComponents.GetUnknownTooltip(roslynSymbol) - }; - symbolInfoNode.FitContent = true; - symbolInfoNode.AutowrapMode = TextServer.AutowrapMode.Off; - symbolInfoNode.SetAnchorsPreset(Control.LayoutPreset.FullRect); - - panel.AddChild(symbolInfoNode); - var vboxContainer = new VBoxContainer(); - vboxContainer.AddThemeConstantOverride("separation", 0); - vboxContainer.AddChild(panel); - tooltipWindow.AddChild(vboxContainer); - tooltipWindow.ChildControlsChanged(); - AddChild(tooltipWindow); - - tooltipWindow.Position = new Vector2I((int)globalMousePosition.X, (int)startSymbolCharGlobalPos.Y + lineHeight); - tooltipWindow.Popup(); - } - private void OnCaretChanged() { _selectionStartCol = GetSelectionFromColumn(); diff --git a/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit_SymbolHover.cs b/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit_SymbolHover.cs new file mode 100644 index 0000000..491899c --- /dev/null +++ b/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit_SymbolHover.cs @@ -0,0 +1,144 @@ +using Godot; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; +using Timer = Godot.Timer; + +namespace SharpIDE.Godot.Features.CodeEditor; + +public partial class SharpIdeCodeEdit +{ + private Timer? _symbolHoverTimer = null!; + + private void CloseSymbolHoverWindow() + { + _symbolHoverTimer?.EmitSignal(Timer.SignalName.Timeout); + _symbolHoverTimer = null; + } + + // This method is a bit of a disaster - we create an additional invisible Window, so that the tooltip window doesn't disappear while the mouse is over the hovered symbol + private async void OnSymbolHovered(string symbol, long line, long column) + { + if (HasFocus() is false) + return; // only show if we have focus, every tab is currently listening for this event, maybe find a better way + var globalMousePosition = + GetGlobalMousePosition(); // don't breakpoint before this, else your mouse position will be wrong + var lineHeight = GetLineHeight(); + GD.Print($"Symbol hovered: {symbol} at line {line}, column {column}"); + + var (roslynSymbol, linePositionSpan) = + await _roslynAnalysis.LookupSymbol(_currentFile, new LinePosition((int)line, (int)column)); + if (roslynSymbol is null || linePositionSpan is null) + { + return; + } + + var symbolNameHoverWindow = new Window(); + symbolNameHoverWindow.WrapControls = true; + symbolNameHoverWindow.Unresizable = true; + symbolNameHoverWindow.Transparent = true; + symbolNameHoverWindow.Borderless = true; + symbolNameHoverWindow.PopupWMHint = true; + symbolNameHoverWindow.PopupWindow = true; + symbolNameHoverWindow.MinimizeDisabled = true; + symbolNameHoverWindow.MaximizeDisabled = true; + symbolNameHoverWindow.Exclusive = false; + symbolNameHoverWindow.Transient = true; + symbolNameHoverWindow.TransientToFocused = true; + symbolNameHoverWindow.Unfocusable = true; + // To debug location, make type a PopupPanel, and uncomment + //symbolNameHoverWindow.AddThemeStyleboxOverride("panel", new StyleBoxFlat { BgColor = new Color(1, 0, 0, 0.5f) }); + + var startSymbolCharRect = + GetRectAtLineColumn(linePositionSpan.Value.Start.Line, linePositionSpan.Value.Start.Character + 1); + var endSymbolCharRect = + GetRectAtLineColumn(linePositionSpan.Value.End.Line, linePositionSpan.Value.End.Character); + symbolNameHoverWindow.Size = new Vector2I(endSymbolCharRect.End.X - startSymbolCharRect.Position.X, lineHeight); + + var globalPosition = GetGlobalPosition(); + var startSymbolCharGlobalPos = startSymbolCharRect.Position + globalPosition; + var endSymbolCharGlobalPos = endSymbolCharRect.Position + globalPosition; + + AddChild(symbolNameHoverWindow); + symbolNameHoverWindow.Position = new Vector2I((int)startSymbolCharGlobalPos.X, (int)endSymbolCharGlobalPos.Y); + symbolNameHoverWindow.Popup(); + + var tooltipWindow = new Window(); + tooltipWindow.WrapControls = true; + tooltipWindow.Unresizable = true; + tooltipWindow.Transparent = true; + tooltipWindow.Borderless = true; + tooltipWindow.PopupWMHint = true; + tooltipWindow.PopupWindow = true; + tooltipWindow.MinimizeDisabled = true; + tooltipWindow.MaximizeDisabled = true; + tooltipWindow.Exclusive = false; + tooltipWindow.Transient = true; + tooltipWindow.TransientToFocused = true; + tooltipWindow.Unfocusable = true; + + var timer = new Timer { WaitTime = 0.05f, OneShot = true, Autostart = false }; + tooltipWindow.AddChild(timer); + timer.Timeout += () => + { + tooltipWindow.QueueFree(); + symbolNameHoverWindow.QueueFree(); + }; + _symbolHoverTimer = timer; + + tooltipWindow.MouseExited += () => timer.Start(); + tooltipWindow.MouseEntered += () => timer.Stop(); + symbolNameHoverWindow.MouseExited += () => timer.Start(); + symbolNameHoverWindow.MouseEntered += () => timer.Stop(); + + var styleBox = new StyleBoxFlat + { + BgColor = new Color("2b2d30"), + BorderColor = new Color("3e4045"), + BorderWidthTop = 1, + BorderWidthBottom = 1, + BorderWidthLeft = 1, + BorderWidthRight = 1, + CornerRadiusBottomLeft = 4, + CornerRadiusBottomRight = 4, + CornerRadiusTopLeft = 4, + CornerRadiusTopRight = 4, + ShadowSize = 2, + ShadowColor = new Color(0, 0, 0, 0.5f), + ExpandMarginTop = -2, // negative margin seems to fix shadow being cut off? + ExpandMarginBottom = -2, + ExpandMarginLeft = -2, + ExpandMarginRight = -2, + ContentMarginTop = 10, + ContentMarginBottom = 10, + ContentMarginLeft = 12, + ContentMarginRight = 12 + }; + var panel = new PanelContainer(); + panel.AddThemeStyleboxOverride("panel", styleBox); + + var symbolInfoNode = roslynSymbol switch + { + IMethodSymbol methodSymbol => SymbolInfoComponents.GetMethodSymbolInfo(methodSymbol), + INamedTypeSymbol namedTypeSymbol => SymbolInfoComponents.GetNamedTypeSymbolInfo(namedTypeSymbol), + IPropertySymbol propertySymbol => SymbolInfoComponents.GetPropertySymbolInfo(propertySymbol), + IFieldSymbol fieldSymbol => SymbolInfoComponents.GetFieldSymbolInfo(fieldSymbol), + IParameterSymbol parameterSymbol => SymbolInfoComponents.GetParameterSymbolInfo(parameterSymbol), + ILocalSymbol localSymbol => SymbolInfoComponents.GetLocalVariableSymbolInfo(localSymbol), + _ => SymbolInfoComponents.GetUnknownTooltip(roslynSymbol) + }; + symbolInfoNode.FitContent = true; + symbolInfoNode.AutowrapMode = TextServer.AutowrapMode.Off; + symbolInfoNode.SetAnchorsPreset(Control.LayoutPreset.FullRect); + + panel.AddChild(symbolInfoNode); + var vboxContainer = new VBoxContainer(); + vboxContainer.AddThemeConstantOverride("separation", 0); + vboxContainer.AddChild(panel); + tooltipWindow.AddChild(vboxContainer); + tooltipWindow.ChildControlsChanged(); + AddChild(tooltipWindow); + + tooltipWindow.Position = new Vector2I((int)globalMousePosition.X, (int)startSymbolCharGlobalPos.Y + lineHeight); + tooltipWindow.Popup(); + } +} \ No newline at end of file