Files
SharpIDE/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit_Completions_Draw.cs
2026-01-27 22:41:26 +10:00

311 lines
12 KiB
C#

using System.Collections.Immutable;
using Godot;
using SharpIDE.Application.Features.Analysis;
namespace SharpIDE.Godot.Features.CodeEditor;
public partial class SharpIdeCodeEdit
{
private ImmutableArray<SharpIdeCompletionItem> _codeCompletionOptions = [];
private Rect2I _codeCompletionRect = new Rect2I();
private Rect2I _codeCompletionScrollRect = new Rect2I();
private Vector2I _codeHintMinsize = new Vector2I();
private Vector2I? _completionTriggerPosition;
private int _codeCompletionLineOfs = 0;
private int _codeCompletionForceItemCenter = -1;
private int _codeCompletionCurrentSelected = 0;
private bool _isCodeCompletionScrollHovered = false;
private bool _isCodeCompletionScrollPressed = false;
private const int MaxLines = 7;
private int? GetCompletionOptionAtPoint(Vector2I point)
{
if (!_codeCompletionRect.HasPoint(point)) return null;
int rowHeight = GetLineHeight();
int relativeY = point.Y - _codeCompletionRect.Position.Y;
int lineIndex = relativeY / rowHeight + _codeCompletionLineOfs;
if (lineIndex < 0 || lineIndex >= _codeCompletionOptions.Length) return null;
return lineIndex;
}
private void DrawCompletionsPopup()
{
var drawCodeCompletion = _codeCompletionOptions.Length > 0;
var drawCodeHint = false;
var codeHintDrawBelow = false;
if (!drawCodeCompletion) return;
// originally from theme cache
const int codeCompletionIconSeparation = 4;
var codeCompletionMinimumSize = new Vector2I(50, 50);
var lineSpacing = 2;
var themeScrollWidth = 6;
//
var font = GetThemeFont(ThemeStringNames.Font);
var fontSize = GetThemeFontSize(ThemeStringNames.FontSize);
var ci = _aboveCanvasItemRid!.Value;
var availableCompletions = _codeCompletionOptions.Length;
var completionsToDisplay = Math.Min(availableCompletions, MaxLines);
var rowHeight = GetLineHeight();
var iconAreaSize = new Vector2I(rowHeight, rowHeight);
var completionMaxWidth = 200;
var codeCompletionLongestLine = Math.Min(completionMaxWidth,
_codeCompletionOptions.MaxBy(s => s.CompletionItem.DisplayText.Length)!.CompletionItem.DisplayText.Length * fontSize);
codeCompletionLongestLine = 500;
_codeCompletionRect.Size = new Vector2I(
codeCompletionLongestLine + codeCompletionIconSeparation + iconAreaSize.X + 2,
completionsToDisplay * rowHeight
);
var caretPos = (Vector2I)GetCaretDrawPos();
var totalHeight = codeCompletionMinimumSize.Y + _codeCompletionRect.Size.Y;
float minY = caretPos.Y - rowHeight;
float maxY = caretPos.Y + rowHeight + totalHeight;
// if (drawCodeHint)
// {
// if (codeHintDrawBelow)
// {
// maxY += codeHintMinsize.Y;
// }
// else
// {
// minY -= codeHintMinsize. Y;
// }
// }
bool canFitCompletionAbove = minY > totalHeight;
var sharpIdeCodeEditSize = GetSize();
bool canFitCompletionBelow = maxY <= sharpIdeCodeEditSize.Y;
bool shouldPlaceAbove = !canFitCompletionBelow && canFitCompletionAbove;
if (!canFitCompletionBelow && !canFitCompletionAbove)
{
float spaceAbove = caretPos.Y - rowHeight;
float spaceBelow = sharpIdeCodeEditSize.Y - caretPos.Y;
shouldPlaceAbove = spaceAbove > spaceBelow;
// Reduce the line count and recalculate heights to better fit the completion popup.
float spaceAvail;
if (shouldPlaceAbove)
{
spaceAvail = spaceAbove - codeCompletionMinimumSize.Y;
}
else
{
spaceAvail = spaceBelow - codeCompletionMinimumSize.Y;
}
int maxLinesFit = Mathf.Max(1, (int)(spaceAvail / rowHeight));
completionsToDisplay = Mathf.Min(completionsToDisplay, maxLinesFit);
_codeCompletionRect.Size = new Vector2I(_codeCompletionRect.Size.X, completionsToDisplay * rowHeight);
totalHeight = codeCompletionMinimumSize.Y + _codeCompletionRect.Size.Y;
}
if (shouldPlaceAbove)
{
_codeCompletionRect.Position = new Vector2I(
_codeCompletionRect.Position.X,
(caretPos.Y - totalHeight - rowHeight) + lineSpacing
);
if (drawCodeHint && !codeHintDrawBelow)
{
_codeCompletionRect.Position = new Vector2I(
_codeCompletionRect.Position.X,
_codeCompletionRect.Position.Y - _codeHintMinsize.Y
);
}
}
else
{
_codeCompletionRect.Position = new Vector2I(
_codeCompletionRect.Position.X,
caretPos.Y + (lineSpacing / 2)
);
if (drawCodeHint && codeHintDrawBelow)
{
_codeCompletionRect.Position = new Vector2I(
_codeCompletionRect.Position.X,
_codeCompletionRect.Position.Y + _codeHintMinsize.Y
);
}
}
var scrollWidth = availableCompletions > MaxLines ? themeScrollWidth : 0;
// TODO: Fix
var codeCompletionBase = "";
const int iconOffset = 25;
// Desired X position for the popup to start at
int desiredX = _completionTriggerPosition!.Value.X - iconOffset;
// Calculate the maximum X allowed so the popup stays inside the parent
int maxX = (int)sharpIdeCodeEditSize.X - _codeCompletionRect.Size.X - scrollWidth;
// Clamp the X position so it never overflows to the right
int finalX = Math.Min(desiredX, maxX);
_codeCompletionRect.Position = new Vector2I(finalX, _codeCompletionRect.Position.Y);
// var completionStyle = GetThemeStylebox(ThemeStringNames.Completion);
// // I don't know what this is used for, but it puts a weird block box around the completions
// completionStyle.Draw(
// ci,
// new Rect2(
// _codeCompletionRect.Position - completionStyle.GetOffset(),
// _codeCompletionRect.Size + codeCompletionMinimumSize + new Vector2I(scrollWidth, 0)
// )
// );
var codeCompletionBackgroundColor = GetThemeColor(ThemeStringNames.CompletionBackgroundColor);
if (codeCompletionBackgroundColor.A > 0.01f)
{
RenderingServer.Singleton.CanvasItemAddRect(
ci,
new Rect2(_codeCompletionRect.Position, _codeCompletionRect.Size + new Vector2I(scrollWidth, 0)),
codeCompletionBackgroundColor
);
}
_codeCompletionScrollRect.Position = _codeCompletionRect.Position + new Vector2I(_codeCompletionRect.Size.X, 0);
_codeCompletionScrollRect.Size = new Vector2I(scrollWidth, _codeCompletionRect.Size.Y);
_codeCompletionLineOfs = Mathf.Clamp(
(_codeCompletionForceItemCenter < 0 ? _codeCompletionCurrentSelected : _codeCompletionForceItemCenter) -
completionsToDisplay / 2,
0,
availableCompletions - completionsToDisplay
);
var codeCompletionSelectedColor = GetThemeColor(ThemeStringNames.CompletionSelectedColor);
RenderingServer.Singleton.CanvasItemAddRect(
ci,
new Rect2(
new Vector2(
_codeCompletionRect.Position.X,
_codeCompletionRect.Position.Y + (_codeCompletionCurrentSelected - _codeCompletionLineOfs) * rowHeight
),
new Vector2(_codeCompletionRect.Size.X, rowHeight)
),
codeCompletionSelectedColor
);
// TODO: Cache
string lang = OS.GetLocale();
for (int i = 0; i < completionsToDisplay; i++)
{
int l = _codeCompletionLineOfs + i;
if (l < 0 || l >= availableCompletions)
{
GD.PushError($"Invalid line index: {l}");
continue;
}
var sharpIdeCompletionItem = _codeCompletionOptions[l];
var displayText = sharpIdeCompletionItem.CompletionItem.DisplayText;
TextLine tl = new TextLine();
tl.AddString(
displayText,
font,
fontSize,
lang
);
float yofs = (rowHeight - tl.GetSize().Y) / 2;
Vector2 titlePos = new Vector2(
_codeCompletionRect.Position.X,
_codeCompletionRect.Position.Y + i * rowHeight + yofs
);
/* Draw completion icon if it is valid. */
var icon = GetIconForCompletion(sharpIdeCompletionItem);
Rect2 iconArea = new Rect2(
new Vector2(_codeCompletionRect.Position.X, _codeCompletionRect.Position.Y + i * rowHeight),
iconAreaSize
);
if (icon != null)
{
Vector2 iconSize = iconArea.Size * 0.7f;
icon.DrawRect(
ci,
new Rect2(
iconArea.Position + (iconArea.Size - iconSize) / 2,
iconSize
),
false
);
}
titlePos.X = iconArea.Position.X + iconArea.Size.X + codeCompletionIconSeparation;
tl.Width = _codeCompletionRect.Size.X - (iconAreaSize.X + codeCompletionIconSeparation);
tl.Alignment = HorizontalAlignment.Left;
Vector2 matchPos = new Vector2(
_codeCompletionRect.Position.X + iconAreaSize.X + codeCompletionIconSeparation,
_codeCompletionRect.Position.Y + i * rowHeight
);
foreach (var matchSegment in sharpIdeCompletionItem.MatchedSpans ?? [])
{
float matchOffset = font.GetStringSize(
displayText.Substr(0, matchSegment.Start),
HorizontalAlignment.Left,
-1,
fontSize
).X;
float matchLen = font.GetStringSize(
displayText.Substr(matchSegment.Start, matchSegment.Length),
HorizontalAlignment.Left,
-1,
fontSize
).X;
RenderingServer.Singleton.CanvasItemAddRect(
ci,
new Rect2(matchPos + new Vector2(matchOffset, 0), new Vector2(matchLen, rowHeight)),
GetThemeColor(ThemeStringNames.CompletionExistingColor)
);
}
var fontColour = Colors.White;
tl.Draw(ci, titlePos, fontColour);
}
/* Draw a small scroll rectangle to show a position in the options. */
if (scrollWidth > 0)
{
Color scrollColor = _isCodeCompletionScrollHovered || _isCodeCompletionScrollPressed
? GetThemeColor(ThemeStringNames.CompletionScrollHoveredColor)
: GetThemeColor(ThemeStringNames.CompletionScrollColor);
float r = (float)MaxLines / availableCompletions;
float o = (float)_codeCompletionLineOfs / availableCompletions;
RenderingServer.Singleton.CanvasItemAddRect(
ci,
new Rect2(
new Vector2(
_codeCompletionRect.Position.X + _codeCompletionRect.Size.X,
_codeCompletionRect.Position.Y + o * _codeCompletionRect.Size.Y
),
new Vector2(scrollWidth, _codeCompletionRect.Size.Y * r)
),
scrollColor
);
}
}
}