using System.Text.RegularExpressions; using StabilityMatrix.Core.Extensions; namespace StabilityMatrix.Core.Processes; public readonly record struct ProcessOutput { /// /// Parsed text with escape sequences and line endings removed /// public required string Text { get; init; } /// /// Optional Raw output, /// mainly for debug and logging. /// public string? RawText { get; init; } /// /// True if output from stderr, false for stdout. /// public bool IsStdErr { get; init; } /// /// Count of newlines to append to the output. /// (Currently not used) /// public int NewLines { get; init; } /// /// Instruction to clear last n lines /// From carriage return '\r' /// public int CarriageReturn { get; init; } /// /// Instruction to move write cursor up n lines /// From Ansi sequence ESC[#A where # is count of lines /// public int CursorUp { get; init; } /// /// Apc message sent from the subprocess /// public ApcMessage? ApcMessage { get; init; } public static ProcessOutput FromStdOutLine(string text) { return FromLine(text, false); } public static ProcessOutput FromStdErrLine(string text) { return FromLine(text, true); } private static ProcessOutput FromLine(string text, bool isStdErr) { // Parse APC message if (ApcParser.TryParse(text, out var message)) { // Override and return return new ProcessOutput { RawText = text, Text = text, IsStdErr = isStdErr, ApcMessage = message }; } // Normal parsing var originalText = text; // Remove \r from the beginning of the line, and add them to count var clearLines = 0; // Skip if starts with \r\n on windows if (!text.StartsWith(Environment.NewLine)) { clearLines += text.CountStart('\r'); text = text.TrimStart('\r'); } // Also detect Ansi escape for cursor up, treat as clear lines also var cursorUp = 0; if (text.Contains('\u001b')) { var match = Regex.Match(text, @"\x1B\[(\d+)?A"); if (match.Success) { // Default to 1 if no count cursorUp = int.TryParse(match.Groups[1].Value, out var n) ? n : 1; // Set text to be everything up to the match text = text[..match.Index]; } } return new ProcessOutput { RawText = originalText, Text = text, IsStdErr = isStdErr, CarriageReturn = clearLines, CursorUp = cursorUp, }; } }