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());
}
}