From 7a72982797f4b5e1461380a58c8af3ef8fed184f Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Fri, 5 Dec 2025 14:31:53 -0700 Subject: [PATCH 1/3] wip fixes for the new IC10Editor changes --- csharp_mod/Extensions.cs | 11 +++++----- csharp_mod/Formatter.cs | 43 ++++++++++++++++------------------------ csharp_mod/Marshal.cs | 6 +++--- 3 files changed, 25 insertions(+), 35 deletions(-) diff --git a/csharp_mod/Extensions.cs b/csharp_mod/Extensions.cs index 71499a6..0e1f8db 100644 --- a/csharp_mod/Extensions.cs +++ b/csharp_mod/Extensions.cs @@ -42,9 +42,9 @@ public static unsafe class SlangExtensions * Rust allocation after the List is created, there is no need to Drop this memory. * */ - public static Line ToLine(this Vec_FfiToken_t vec, string sourceText) + public static StyledLine ToLine(this Vec_FfiToken_t vec, string sourceText) { - var list = new Line(sourceText); + var tokens = new List(); var currentPtr = vec.ptr; @@ -63,9 +63,8 @@ public static unsafe class SlangExtensions 0, colIndex, token.length, - color, token.token_kind, - 0, + color, token.tooltip.AsString() ); @@ -76,12 +75,12 @@ public static unsafe class SlangExtensions semanticToken.Data = errMsg; semanticToken.Color = ICodeFormatter.ColorError; } - list.AddToken(semanticToken); + tokens.Add(semanticToken); } Ffi.free_ffi_token_vec(vec); - return list; + return new StyledLine(sourceText, tokens); } public static unsafe List ToList(this Vec_FfiDiagnostic_t vec) diff --git a/csharp_mod/Formatter.cs b/csharp_mod/Formatter.cs index 13eaf16..791826d 100644 --- a/csharp_mod/Formatter.cs +++ b/csharp_mod/Formatter.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading; -using System.Timers; using Cysharp.Threading.Tasks; using StationeersIC10Editor; @@ -70,8 +69,9 @@ public class SlangFormatter : ICodeFormatter return this.Lines.RawText; } - public override Line ParseLine(string line) + public override StyledLine ParseLine(string line) { + L.Debug($"Parsing line for syntax highlighting: {line}"); return Marshal.TokenizeLine(line); } @@ -90,8 +90,6 @@ public class SlangFormatter : ICodeFormatter HandleLsp(inputSrc, token).Forget(); } - private void OnTimerElapsed(object sender, ElapsedEventArgs e) { } - private async UniTaskVoid HandleLsp(string inputSrc, CancellationToken cancellationToken) { try @@ -139,33 +137,26 @@ public class SlangFormatter : ICodeFormatter if (line is null) continue; - line.ClearTokens(); - - Dictionary lineDict = Marshal - .TokenizeLine(line.Text) - .Tokens.ToDictionary((t) => t.Column); - if (dict.ContainsKey(lineIndex)) { + var tokens = new List(); foreach (var lineDiagnostic in dict[lineIndex]) { - lineDict[(int)lineDiagnostic.Range.StartCol] = new SemanticToken - { - Column = Math.Abs((int)lineDiagnostic.Range.StartCol), - Length = Math.Abs( - (int)(lineDiagnostic.Range.EndCol - lineDiagnostic.Range.StartCol) - ), - Line = (int)lineIndex, - IsError = true, - Data = lineDiagnostic.Message, - Color = SlangFormatter.ColorError, - }; + tokens.Add( + new SemanticToken( + line: (int)lineIndex, + column: Math.Abs((int)lineDiagnostic.Range.StartCol), + length: Math.Abs( + (int)(lineDiagnostic.Range.EndCol - lineDiagnostic.Range.StartCol) + ), + type: 0, + style: ICodeFormatter.ColorError, + data: lineDiagnostic.Message, + isError: true + ) + ); } - } - - foreach (var token in lineDict.Values) - { - line.AddToken(token); + line.Update(tokens); } } diff --git a/csharp_mod/Marshal.cs b/csharp_mod/Marshal.cs index 3a7b385..e64e964 100644 --- a/csharp_mod/Marshal.cs +++ b/csharp_mod/Marshal.cs @@ -131,11 +131,11 @@ public static class Marshal } } - public static unsafe Line TokenizeLine(string inputString) + public static unsafe StyledLine TokenizeLine(string inputString) { if (string.IsNullOrEmpty(inputString) || !EnsureLibLoaded()) { - return new Line(inputString); + return new StyledLine(inputString ?? ""); } fixed (char* ptrInputStr = inputString) @@ -147,7 +147,7 @@ public static class Marshal }; var tokens = Ffi.tokenize_line(strRef); - + L.Debug($"Tokenized line '{inputString}' into {tokens.len} tokens."); return tokens.ToLine(inputString); } } From 979d32b413677647c6f03f6d1dc6e9cbaa25978c Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Fri, 5 Dec 2025 14:53:56 -0700 Subject: [PATCH 2/3] Working syntax highlighting again --- csharp_mod/Extensions.cs | 4 ++-- csharp_mod/Formatter.cs | 12 ++++++++---- csharp_mod/Marshal.cs | 6 +++--- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/csharp_mod/Extensions.cs b/csharp_mod/Extensions.cs index 0e1f8db..6a7e8f2 100644 --- a/csharp_mod/Extensions.cs +++ b/csharp_mod/Extensions.cs @@ -42,7 +42,7 @@ public static unsafe class SlangExtensions * Rust allocation after the List is created, there is no need to Drop this memory. * */ - public static StyledLine ToLine(this Vec_FfiToken_t vec, string sourceText) + public static List ToTokenList(this Vec_FfiToken_t vec) { var tokens = new List(); @@ -80,7 +80,7 @@ public static unsafe class SlangExtensions Ffi.free_ffi_token_vec(vec); - return new StyledLine(sourceText, tokens); + return tokens; } public static unsafe List ToList(this Vec_FfiDiagnostic_t vec) diff --git a/csharp_mod/Formatter.cs b/csharp_mod/Formatter.cs index 791826d..e4fd74a 100644 --- a/csharp_mod/Formatter.cs +++ b/csharp_mod/Formatter.cs @@ -72,7 +72,7 @@ public class SlangFormatter : ICodeFormatter public override StyledLine ParseLine(string line) { L.Debug($"Parsing line for syntax highlighting: {line}"); - return Marshal.TokenizeLine(line); + return new StyledLine(line, Marshal.TokenizeLine(line)); } private void HandleCodeChanged() @@ -137,12 +137,15 @@ public class SlangFormatter : ICodeFormatter if (line is null) continue; + // 1. Get base syntax tokens + var allTokens = Marshal.TokenizeLine(line.Text); + + // 2. Overlay error tokens if diagnostics exist for this line if (dict.ContainsKey(lineIndex)) { - var tokens = new List(); foreach (var lineDiagnostic in dict[lineIndex]) { - tokens.Add( + allTokens.Add( new SemanticToken( line: (int)lineIndex, column: Math.Abs((int)lineDiagnostic.Range.StartCol), @@ -156,8 +159,9 @@ public class SlangFormatter : ICodeFormatter ) ); } - line.Update(tokens); } + + line.Update(allTokens); } _linesWithErrors = new HashSet(dict.Keys); diff --git a/csharp_mod/Marshal.cs b/csharp_mod/Marshal.cs index e64e964..b03696f 100644 --- a/csharp_mod/Marshal.cs +++ b/csharp_mod/Marshal.cs @@ -131,11 +131,11 @@ public static class Marshal } } - public static unsafe StyledLine TokenizeLine(string inputString) + public static unsafe List TokenizeLine(string inputString) { if (string.IsNullOrEmpty(inputString) || !EnsureLibLoaded()) { - return new StyledLine(inputString ?? ""); + return new List(); } fixed (char* ptrInputStr = inputString) @@ -148,7 +148,7 @@ public static class Marshal var tokens = Ffi.tokenize_line(strRef); L.Debug($"Tokenized line '{inputString}' into {tokens.len} tokens."); - return tokens.ToLine(inputString); + return tokens.ToTokenList(); } } From c95f35e68c22191a3bb84c1ac13bf72715b6cdaf Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Fri, 5 Dec 2025 15:36:15 -0700 Subject: [PATCH 3/3] Working tooltips. Feature paraody with pre-ic10editor update is complete. --- csharp_mod/Formatter.cs | 137 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 134 insertions(+), 3 deletions(-) diff --git a/csharp_mod/Formatter.cs b/csharp_mod/Formatter.cs index e4fd74a..18ece90 100644 --- a/csharp_mod/Formatter.cs +++ b/csharp_mod/Formatter.cs @@ -27,6 +27,7 @@ public class SlangFormatter : ICodeFormatter public static readonly uint ColorOperator = ColorFromHTML("#D4D4D4"); private HashSet _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> dict) { - var linesToRefresh = new HashSet(dict.Keys); - linesToRefresh.UnionWith(_linesWithErrors); + HashSet 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(); + for (int i = 0; i < this.Lines.Count; i++) + { + linesToRefresh.Add((uint)i); + } + } + else + { + linesToRefresh = new HashSet(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(dict.Keys); } + + // Helper to map SemanticToken data (tooltips/errors) back to the tokens in the line + private void ReattachMetadata(StyledLine line, List 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 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 WrapText(string text, int maxLineLength) + { + var lines = new List(); + 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; + } }