// Copyright (c) Microsoft. All rights reserved. using Harness.ConsoleReactiveFramework; namespace Harness.ConsoleReactiveComponents; /// /// Props for . /// public record TextInputProps : ConsoleReactiveProps { /// Gets the prompt string displayed on the left (e.g. "> " or "user > "). public string Prompt { get; init; } = "> "; /// Gets the text content to render to the right of the prompt. public string Text { get; init; } = ""; /// Gets the placeholder text shown in dark grey when is empty. public string Placeholder { get; init; } = ""; } /// /// A component that renders a prompt with text input. Supports multi-line text /// where continuation lines are indented to align with the text start position /// (i.e. the column after the prompt). /// public class TextInput : ConsoleReactiveComponent { /// /// Calculates the height (in rows) required to render the prompt and text /// given the available width. /// /// The text input props. /// The total available width in columns. /// The number of rows needed. public static int CalculateHeight(TextInputProps props, int availableWidth) { int promptLength = props.Prompt.Length; int textWidth = availableWidth - promptLength; if (textWidth <= 0 || props.Text.Length == 0) { return 1; } int lines = 1; int remaining = props.Text.Length - textWidth; while (remaining > 0) { lines++; remaining -= textWidth; } return lines; } /// public override void RenderCore(TextInputProps props, ConsoleReactiveState state) { int promptLength = props.Prompt.Length; int textWidth = props.Width - promptLength; string indent = new(' ', promptLength); // First line: prompt + start of text Console.Write(AnsiEscapes.MoveCursor(props.Y, props.X)); Console.Write(AnsiEscapes.EraseEntireLine); Console.Write(props.Prompt); if (textWidth <= 0 || props.Text.Length == 0) { // Show placeholder if text is empty if (props.Text.Length == 0 && props.Placeholder.Length > 0 && textWidth > 0) { Console.Write(AnsiEscapes.SetForegroundColor(ConsoleColor.DarkGray)); Console.Write(" "); Console.Write(props.Placeholder[..Math.Min(props.Placeholder.Length, textWidth - 1)]); Console.Write(AnsiEscapes.ResetAttributes); } return; } int offset = 0; int firstChunk = Math.Min(textWidth, props.Text.Length); Console.Write(props.Text[offset..firstChunk]); offset = firstChunk; // Continuation lines: indented to align with text start int row = 1; while (offset < props.Text.Length) { int chunk = Math.Min(textWidth, props.Text.Length - offset); Console.Write(AnsiEscapes.MoveCursor(props.Y + row, props.X)); Console.Write(AnsiEscapes.EraseEntireLine); Console.Write(indent); Console.Write(props.Text[offset..(offset + chunk)]); offset += chunk; row++; } } }