using System; using System.Collections.Generic; using System.Text.RegularExpressions; namespace OpenNest.CNC; /// /// A character range in generated G-code assigned by one highlighting rule. /// /// Zero-based UTF-16 index of the first colored character. /// Number of UTF-16 characters to color. /// /// Zero-based rule index: 0 comments, 1 motion modes (G90/G91), 2 rapid moves (G00), /// 3 linear moves (G01), 4 arcs (G02/G03). /// public readonly record struct HighlightSpan(int Index, int Length, int RuleIndex); /// Computes cosmetic G-code highlight spans without changing the generated text. public static class ProgramHighlighting { private static readonly Regex[] Rules = { new(@"^;.*$", RegexOptions.Multiline, TimeSpan.FromMilliseconds(100)), new(@"^G9[01]\b", RegexOptions.Multiline, TimeSpan.FromMilliseconds(100)), new(@"^G00\b", RegexOptions.Multiline, TimeSpan.FromMilliseconds(100)), new(@"^G01\b", RegexOptions.Multiline, TimeSpan.FromMilliseconds(100)), new(@"^G0[23]\b", RegexOptions.Multiline, TimeSpan.FromMilliseconds(100)), }; /// /// Materializes all matches in rule/application order (later rules overwrite earlier ones). /// A timeout propagates before any result is published; no partial span list escapes. /// The timeout is per regex match, not a deadline for the complete operation. /// /// A rule exceeded its match budget. public static IReadOnlyList ComputeSpans(string text) { ArgumentNullException.ThrowIfNull(text); var spans = new List(); for (var ruleIndex = 0; ruleIndex < Rules.Length; ruleIndex++) { // MatchCollection is lazy: enumerate every rule before returning any spans. foreach (Match match in Rules[ruleIndex].Matches(text)) spans.Add(new HighlightSpan(match.Index, match.Length, ruleIndex)); } return spans.AsReadOnly(); } }