using TinyTUI.Input; using TinyTUI.Text; namespace TinyTUI.Components; /// /// 支持多行文本编辑的基础编辑器组件 /// public sealed partial class Editor(ITextMeasurer? textMeasurer = null, KeybindingRegistry? keybindings = null) : IInputComponent, IFocusableComponent { private readonly ITextMeasurer _textMeasurer = textMeasurer ?? new TerminalTextMeasurer(); private readonly KeybindingRegistry _keybindings = keybindings ?? KeybindingRegistry.CreateDefault(); private readonly List _lines = [string.Empty]; private readonly Stack _undoStack = []; private readonly Stack _redoStack = []; private readonly List _history = []; private int _cursorRow; private int _cursorColumn; private int _historyIndex = -1; private string _historyDraft = string.Empty; private int _lastRenderWidth = 80; private int _scrollOffset; private int? _preferredVisualColumn; /// /// 获取或设置编辑器高度 /// public int Height { get; set; } = 5; /// /// 获取或设置是否按可用宽度软换行 /// public bool SoftWrap { get; set; } = true; /// /// 获取或设置空编辑器的输入提示 /// public string Placeholder { get; set; } = string.Empty; /// public bool Focused { get; set; } /// /// 获取当前编辑器文本 /// public string Value { get => string.Join('\n', _lines); set => SetValue(value); } /// /// 在文本变化时触发 /// public Action? OnChanged { get; set; } /// /// 在用户取消编辑时触发 /// public Action? OnCanceled { get; set; } /// /// 在用户提交编辑内容时触发 /// public Action? OnSubmitted { get; set; } /// public void HandleInput(TuiInputEvent input) { switch (input) { case { Kind: TuiInputEventKind.Text }: Edit(() => InsertText(input.Value)); break; case { Kind: TuiInputEventKind.Paste }: Edit(() => InsertText(input.Value.Replace("\r\n", "\n"))); break; case { Kind: TuiInputEventKind.Key }: HandleKey(input); break; } } /// /// 提交当前编辑内容 /// public void Submit() => OnSubmitted?.Invoke(Value); /// /// 取消当前编辑 /// public void Cancel() => OnCanceled?.Invoke(); /// /// 清空编辑器内容 /// public void Clear() => SetValue(string.Empty); /// /// 根据动作映射处理多行编辑器快捷键 /// private void HandleKey(TuiInputEvent input) { if (_keybindings.Matches(input, TuiKeybindings.EditorUndo)) { Undo(); return; } if (_keybindings.Matches(input, TuiKeybindings.EditorRedo)) { Redo(); return; } if (_keybindings.Matches(input, TuiKeybindings.EditorClear)) { Edit(ClearText); return; } if (_keybindings.Matches(input, TuiKeybindings.EditorCursorWordLeft)) { MoveWordLeft(); return; } if (_keybindings.Matches(input, TuiKeybindings.EditorCursorWordRight)) { MoveWordRight(); return; } if (_keybindings.Matches(input, TuiKeybindings.EditorDeleteWordBackward)) { Edit(DeleteWordBackward); return; } if (_keybindings.Matches(input, TuiKeybindings.EditorDeleteWordForward)) { Edit(DeleteWordForward); return; } if (_keybindings.Matches(input, TuiKeybindings.EditorSubmit)) { Submit(); return; } if (_keybindings.Matches(input, TuiKeybindings.EditorNewLine)) { Edit(SplitLine); return; } if (_keybindings.Matches(input, TuiKeybindings.EditorDeleteCharBackward)) { Edit(Backspace); return; } if (_keybindings.Matches(input, TuiKeybindings.EditorDeleteCharForward)) { Edit(Delete); return; } if (_keybindings.Matches(input, TuiKeybindings.EditorCursorLeft)) { MoveLeft(); return; } if (_keybindings.Matches(input, TuiKeybindings.EditorCursorRight)) { MoveRight(); return; } if (_keybindings.Matches(input, TuiKeybindings.EditorCursorUp)) { if (!TryNavigateHistory(-1)) MoveVertical(-1); return; } if (_keybindings.Matches(input, TuiKeybindings.EditorCursorDown)) { if (!TryNavigateHistory(1)) MoveVertical(1); return; } if (_keybindings.Matches(input, TuiKeybindings.EditorCursorLineStart)) { MoveToLineStart(); return; } if (_keybindings.Matches(input, TuiKeybindings.EditorCursorLineEnd)) { MoveToLineEnd(); return; } if (_keybindings.Matches(input, TuiKeybindings.InputCancel)) Cancel(); } }