Working tooltips. Feature paraody with pre-ic10editor update is complete.
This commit is contained in:
@@ -27,6 +27,7 @@ public class SlangFormatter : ICodeFormatter
|
||||
public static readonly uint ColorOperator = ColorFromHTML("#D4D4D4");
|
||||
|
||||
private HashSet<uint> _linesWithErrors = new();
|
||||
private int _lastLineCount = -1;
|
||||
|
||||
public SlangFormatter()
|
||||
: base()
|
||||
@@ -72,7 +73,20 @@ public class SlangFormatter : ICodeFormatter
|
||||
public override StyledLine ParseLine(string line)
|
||||
{
|
||||
L.Debug($"Parsing line for syntax highlighting: {line}");
|
||||
return new StyledLine(line, Marshal.TokenizeLine(line));
|
||||
|
||||
// We create the line first
|
||||
var styledLine = new StyledLine(line);
|
||||
|
||||
// We get the semantic tokens (color + data)
|
||||
var tokens = Marshal.TokenizeLine(line);
|
||||
|
||||
// We call update to create the basic tokens
|
||||
styledLine.Update(tokens);
|
||||
|
||||
// CRITICAL FIX: We must manually re-attach metadata because StyledLine.Update() drops it.
|
||||
ReattachMetadata(styledLine, tokens);
|
||||
|
||||
return styledLine;
|
||||
}
|
||||
|
||||
private void HandleCodeChanged()
|
||||
@@ -123,8 +137,27 @@ public class SlangFormatter : ICodeFormatter
|
||||
// This runs on the Main Thread
|
||||
private void ApplyDiagnostics(Dictionary<uint, IGrouping<uint, Diagnostic>> dict)
|
||||
{
|
||||
var linesToRefresh = new HashSet<uint>(dict.Keys);
|
||||
linesToRefresh.UnionWith(_linesWithErrors);
|
||||
HashSet<uint> linesToRefresh;
|
||||
|
||||
// CRITICAL FIX FOR LINE SHIFTS:
|
||||
// If the line count has changed (lines added/deleted), indices have shifted.
|
||||
// We must refresh ALL lines to ensure any line that shifted into a new position
|
||||
// gets scrubbed of its old visual state.
|
||||
if (this.Lines.Count != _lastLineCount)
|
||||
{
|
||||
linesToRefresh = new HashSet<uint>();
|
||||
for (int i = 0; i < this.Lines.Count; i++)
|
||||
{
|
||||
linesToRefresh.Add((uint)i);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
linesToRefresh = new HashSet<uint>(dict.Keys);
|
||||
linesToRefresh.UnionWith(_linesWithErrors);
|
||||
}
|
||||
|
||||
_lastLineCount = this.Lines.Count;
|
||||
|
||||
foreach (var lineIndex in linesToRefresh)
|
||||
{
|
||||
@@ -161,9 +194,107 @@ public class SlangFormatter : ICodeFormatter
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Update the line (this clears existing tokens and uses the list we just built)
|
||||
line.Update(allTokens);
|
||||
|
||||
// 4. CRITICAL FIX: Re-attach metadata that Update() dropped
|
||||
ReattachMetadata(line, allTokens);
|
||||
}
|
||||
|
||||
_linesWithErrors = new HashSet<uint>(dict.Keys);
|
||||
}
|
||||
|
||||
// Helper to map SemanticToken data (tooltips/errors) back to the tokens in the line
|
||||
private void ReattachMetadata(StyledLine line, List<SemanticToken> semanticTokens)
|
||||
{
|
||||
foreach (var semToken in semanticTokens)
|
||||
{
|
||||
// Skip tokens without data
|
||||
if (string.IsNullOrEmpty(semToken.Data))
|
||||
continue;
|
||||
|
||||
// Find the corresponding Token in the line
|
||||
var token = line.GetTokenAt(semToken.Column);
|
||||
if (token != null)
|
||||
{
|
||||
// Wrap text to avoid "wide as monitor" tooltips
|
||||
var wrappedMessage = WrapText(semToken.Data, 50);
|
||||
var msgText = CreateStyledTextFromLines(
|
||||
wrappedMessage,
|
||||
semToken.IsError ? ICodeFormatter.ColorError : ICodeFormatter.ColorDefault
|
||||
);
|
||||
|
||||
if (semToken.IsError)
|
||||
{
|
||||
token.Error = msgText;
|
||||
}
|
||||
else
|
||||
{
|
||||
token.Tooltip = msgText;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to create a StyledText object from a list of strings
|
||||
private StyledText CreateStyledTextFromLines(List<string> lines, uint color)
|
||||
{
|
||||
var styledText = new StyledText();
|
||||
foreach (var lineContent in lines)
|
||||
{
|
||||
var l = new StyledLine(lineContent);
|
||||
l.Add(new Token(0, lineContent, new Style(color)));
|
||||
styledText.Add(l);
|
||||
}
|
||||
return styledText;
|
||||
}
|
||||
|
||||
// Text wrapper that preserves paragraph structure but enforces width
|
||||
private List<string> WrapText(string text, int maxLineLength)
|
||||
{
|
||||
var lines = new List<string>();
|
||||
if (string.IsNullOrEmpty(text))
|
||||
return lines;
|
||||
|
||||
// Normalize newlines and split by paragraph
|
||||
var paragraphs = text.Replace("\r\n", "\n").Split('\n');
|
||||
|
||||
foreach (var paragraph in paragraphs)
|
||||
{
|
||||
// Preserve empty lines (paragraph breaks)
|
||||
if (string.IsNullOrWhiteSpace(paragraph))
|
||||
{
|
||||
lines.Add("");
|
||||
continue;
|
||||
}
|
||||
|
||||
var words = paragraph.Split(' ');
|
||||
var currentLine = "";
|
||||
|
||||
foreach (var word in words)
|
||||
{
|
||||
// If adding the next word exceeds max length...
|
||||
if (currentLine.Length + word.Length + 1 > maxLineLength)
|
||||
{
|
||||
// Push current line if it has content
|
||||
if (currentLine.Length > 0)
|
||||
{
|
||||
lines.Add(currentLine.TrimEnd());
|
||||
currentLine = "";
|
||||
}
|
||||
}
|
||||
|
||||
if (currentLine.Length > 0)
|
||||
currentLine += " ";
|
||||
currentLine += word;
|
||||
}
|
||||
|
||||
// Flush remaining content
|
||||
if (currentLine.Length > 0)
|
||||
{
|
||||
lines.Add(currentLine.TrimEnd());
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user