using System.Text; using TinyTUI.Input; using TinyTUI.Rendering; using TinyTUI.Text; using TinyTUI.Theme; namespace TinyTUI.Components; /// /// 支持文本输入 提交和取消的单行输入组件 /// public sealed class Input( ITextMeasurer? textMeasurer = null, KeybindingRegistry? keybindings = null, ITuiTheme? theme = null) : IInputComponent, IFocusableComponent, IKeybindingComponent { private readonly ITextMeasurer _textMeasurer = textMeasurer ?? new TerminalTextMeasurer(); private readonly ITuiTheme _theme = theme ?? TuiTheme.Default; private KeybindingRegistry _keybindings = keybindings ?? KeybindingRegistry.CreateDefault(); /// /// 获取或设置输入框前缀 /// public string Prompt { get; set; } = "> "; /// /// 获取当前输入值 /// public string Value { get; private set; } = string.Empty; /// public bool Focused { get; set; } /// /// 在输入值变化时触发 /// public Action? OnChanged { get; set; } /// /// 在用户提交输入时触发 /// public Action? OnSubmitted { get; set; } /// /// 在用户取消输入时触发 /// public Action? OnCanceled { get; set; } /// public IReadOnlyList Render(int width) { // 只有运行时确认获得焦点后才发出 marker 避免多个输入组件争抢硬件光标 var marker = Focused ? CursorMarker.Marker : string.Empty; var cursor = Focused ? _theme.InputCursor(" ") : string.Empty; var visible = $"{Prompt}{Value}{marker}{cursor}"; return [_textMeasurer.Truncate(visible, width)]; } /// public void SetKeybindings(KeybindingRegistry keybindings) => _keybindings = keybindings ?? throw new ArgumentNullException(nameof(keybindings)); /// public void HandleInput(TuiInputEvent input) { switch (input) { case { Kind: TuiInputEventKind.Text }: SetValue(Value + input.Value); break; case { Kind: TuiInputEventKind.Paste }: SetValue(Value + input.Value.Replace("\r\n", "\n").Replace('\n', ' ')); break; case { Kind: TuiInputEventKind.Key }: HandleKey(input); break; } } /// /// 清空当前输入值 /// public void Clear() => SetValue(string.Empty); /// /// 根据动作映射处理单行输入快捷键 /// private void HandleKey(TuiInputEvent input) { if (_keybindings.Matches(input, TuiKeybindings.EditorDeleteCharBackward)) { Backspace(); return; } if (_keybindings.Matches(input, TuiKeybindings.InputSubmit)) { OnSubmitted?.Invoke(Value); return; } if (_keybindings.Matches(input, TuiKeybindings.InputCancel)) OnCanceled?.Invoke(); } private void SetValue(string value) { Value = value; OnChanged?.Invoke(Value); } /// /// 删除最后一个 Unicode Rune /// private void Backspace() { if (Value.Length == 0) return; var builder = new StringBuilder(); var runes = Value.EnumerateRunes().ToArray(); // 用 Rune 删除避免把 emoji 或代理对拆坏 foreach (var rune in runes[..^1]) builder.Append(rune); SetValue(builder.ToString()); } }