working textarea with example, time to prepare it for the future implementations
This commit is contained in:
@@ -22,7 +22,7 @@ compile_error!(
|
||||
use std::io;
|
||||
use crossterm::{
|
||||
event::{
|
||||
self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers,
|
||||
self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent, KeyModifiers,
|
||||
},
|
||||
execute,
|
||||
terminal::{
|
||||
@@ -47,11 +47,11 @@ use canvas::{
|
||||
};
|
||||
|
||||
/// Enhanced TextArea that demonstrates automatic cursor management
|
||||
/// Now uses direct FormEditor method calls via Deref!
|
||||
struct AutoCursorTextArea {
|
||||
textarea: TextAreaState,
|
||||
has_unsaved_changes: bool,
|
||||
debug_message: String,
|
||||
mode: AppMode,
|
||||
command_buffer: String,
|
||||
}
|
||||
|
||||
@@ -66,7 +66,8 @@ Try different modes:\n\
|
||||
\n\
|
||||
Navigation commands:\n\
|
||||
• hjkl or arrow keys: move cursor\n\
|
||||
• i: enter insert mode\n\
|
||||
• i/a/A/o/O: enter insert mode\n\
|
||||
• w/b/e/W/B/E: word movements\n\
|
||||
• Esc: return to normal mode\n\
|
||||
\n\
|
||||
Watch how the terminal cursor changes automatically!\n\
|
||||
@@ -81,7 +82,6 @@ Press ? for help, F1/F2 for manual cursor control demo.";
|
||||
textarea,
|
||||
has_unsaved_changes: false,
|
||||
debug_message: "🎯 Automatic Cursor Demo - cursor-style feature enabled!".to_string(),
|
||||
mode: AppMode::ReadOnly,
|
||||
command_buffer: String::new(),
|
||||
}
|
||||
}
|
||||
@@ -89,20 +89,21 @@ Press ? for help, F1/F2 for manual cursor control demo.";
|
||||
// === MODE TRANSITIONS WITH AUTOMATIC CURSOR MANAGEMENT ===
|
||||
|
||||
fn enter_insert_mode(&mut self) -> std::io::Result<()> {
|
||||
self.mode = AppMode::Edit;
|
||||
self.textarea.enter_edit_mode(); // 🎯 Direct FormEditor method call via Deref!
|
||||
CursorManager::update_for_mode(AppMode::Edit)?; // 🎯 Automatic: cursor becomes bar |
|
||||
self.debug_message = "✏️ INSERT MODE - Cursor: Steady Bar |".to_string();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn enter_append_mode(&mut self) -> std::io::Result<()> {
|
||||
// Move cursor to end of current line, then enter insert mode
|
||||
self.send_key_to_textarea(KeyCode::End, KeyModifiers::NONE);
|
||||
self.enter_insert_mode()
|
||||
self.textarea.enter_append_mode(); // 🎯 Direct FormEditor method call!
|
||||
CursorManager::update_for_mode(AppMode::Edit)?;
|
||||
self.debug_message = "✏️ INSERT (append) - Cursor: Steady Bar |".to_string();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn exit_to_normal_mode(&mut self) -> std::io::Result<()> {
|
||||
self.mode = AppMode::ReadOnly;
|
||||
self.textarea.exit_edit_mode(); // 🎯 Direct FormEditor method call!
|
||||
CursorManager::update_for_mode(AppMode::ReadOnly)?; // 🎯 Automatic: cursor becomes steady block
|
||||
self.debug_message = "🔒 NORMAL MODE - Cursor: Steady Block █".to_string();
|
||||
Ok(())
|
||||
@@ -119,7 +120,7 @@ Press ? for help, F1/F2 for manual cursor control demo.";
|
||||
|
||||
fn restore_automatic_cursor(&mut self) -> std::io::Result<()> {
|
||||
// Restore automatic cursor based on current mode
|
||||
CursorManager::update_for_mode(self.mode)?;
|
||||
CursorManager::update_for_mode(self.textarea.mode())?; // 🎯 Direct method call!
|
||||
self.debug_message = "🎯 Restored automatic cursor management".to_string();
|
||||
Ok(())
|
||||
}
|
||||
@@ -128,85 +129,93 @@ Press ? for help, F1/F2 for manual cursor control demo.";
|
||||
|
||||
fn handle_textarea_input(&mut self, key: KeyEvent) {
|
||||
self.textarea.input(key);
|
||||
if key.code != KeyCode::Left && key.code != KeyCode::Right
|
||||
&& key.code != KeyCode::Up && key.code != KeyCode::Down
|
||||
&& key.code != KeyCode::Home && key.code != KeyCode::End {
|
||||
self.has_unsaved_changes = true;
|
||||
}
|
||||
self.has_unsaved_changes = true;
|
||||
}
|
||||
|
||||
fn send_key_to_textarea(&mut self, code: KeyCode, modifiers: KeyModifiers) {
|
||||
let key_event = KeyEvent {
|
||||
code,
|
||||
modifiers,
|
||||
kind: KeyEventKind::Press,
|
||||
state: crossterm::event::KeyEventState::NONE,
|
||||
};
|
||||
self.textarea.input(key_event);
|
||||
}
|
||||
|
||||
// === MOVEMENT OPERATIONS (for normal mode) ===
|
||||
// === MOVEMENT OPERATIONS (using direct FormEditor methods!) ===
|
||||
|
||||
fn move_left(&mut self) {
|
||||
self.send_key_to_textarea(KeyCode::Left, KeyModifiers::NONE);
|
||||
self.textarea.move_left(); // 🎯 Direct FormEditor method call!
|
||||
self.update_debug_for_movement("← left");
|
||||
}
|
||||
|
||||
fn move_right(&mut self) {
|
||||
self.send_key_to_textarea(KeyCode::Right, KeyModifiers::NONE);
|
||||
self.textarea.move_right(); // 🎯 Direct FormEditor method call!
|
||||
self.update_debug_for_movement("→ right");
|
||||
}
|
||||
|
||||
fn move_up(&mut self) {
|
||||
self.send_key_to_textarea(KeyCode::Up, KeyModifiers::NONE);
|
||||
self.textarea.move_up(); // 🎯 Direct FormEditor method call!
|
||||
self.update_debug_for_movement("↑ up");
|
||||
}
|
||||
|
||||
fn move_down(&mut self) {
|
||||
self.send_key_to_textarea(KeyCode::Down, KeyModifiers::NONE);
|
||||
self.textarea.move_down(); // 🎯 Direct FormEditor method call!
|
||||
self.update_debug_for_movement("↓ down");
|
||||
}
|
||||
|
||||
fn move_word_next(&mut self) {
|
||||
// Use Alt+f for word forward (from textarea implementation)
|
||||
self.send_key_to_textarea(KeyCode::Char('f'), KeyModifiers::ALT);
|
||||
self.textarea.move_word_next(); // 🎯 Direct FormEditor method call!
|
||||
self.update_debug_for_movement("w: next word");
|
||||
}
|
||||
|
||||
fn move_word_prev(&mut self) {
|
||||
// Use Alt+b for word backward (from textarea implementation)
|
||||
self.send_key_to_textarea(KeyCode::Char('b'), KeyModifiers::ALT);
|
||||
self.textarea.move_word_prev(); // 🎯 Direct FormEditor method call!
|
||||
self.update_debug_for_movement("b: previous word");
|
||||
}
|
||||
|
||||
fn move_word_end(&mut self) {
|
||||
// Use Alt+e for word end (from textarea implementation)
|
||||
self.send_key_to_textarea(KeyCode::Char('e'), KeyModifiers::ALT);
|
||||
self.textarea.move_word_end(); // 🎯 Direct FormEditor method call!
|
||||
self.update_debug_for_movement("e: word end");
|
||||
}
|
||||
|
||||
fn move_word_end_prev(&mut self) {
|
||||
self.textarea.move_word_end_prev(); // 🎯 Direct FormEditor method call!
|
||||
self.update_debug_for_movement("ge: previous word end");
|
||||
}
|
||||
|
||||
fn move_line_start(&mut self) {
|
||||
self.send_key_to_textarea(KeyCode::Home, KeyModifiers::NONE);
|
||||
self.textarea.move_line_start(); // 🎯 Direct FormEditor method call!
|
||||
self.update_debug_for_movement("0: line start");
|
||||
}
|
||||
|
||||
fn move_line_end(&mut self) {
|
||||
self.send_key_to_textarea(KeyCode::End, KeyModifiers::NONE);
|
||||
self.textarea.move_line_end(); // 🎯 Direct FormEditor method call!
|
||||
self.update_debug_for_movement("$: line end");
|
||||
}
|
||||
|
||||
fn move_first_line(&mut self) {
|
||||
// Move to very beginning of text
|
||||
self.send_key_to_textarea(KeyCode::Char('a'), KeyModifiers::CONTROL);
|
||||
self.textarea.move_first_line(); // 🎯 Direct FormEditor method call!
|
||||
self.update_debug_for_movement("gg: first line");
|
||||
}
|
||||
|
||||
fn move_last_line(&mut self) {
|
||||
// Move to very end of text
|
||||
self.send_key_to_textarea(KeyCode::Char('e'), KeyModifiers::CONTROL);
|
||||
self.textarea.move_last_line(); // 🎯 Direct FormEditor method call!
|
||||
self.update_debug_for_movement("G: last line");
|
||||
}
|
||||
|
||||
// === BIG WORD MOVEMENTS ===
|
||||
|
||||
fn move_big_word_next(&mut self) {
|
||||
self.textarea.move_big_word_next(); // 🎯 Direct FormEditor method call!
|
||||
self.update_debug_for_movement("W: next WORD");
|
||||
}
|
||||
|
||||
fn move_big_word_prev(&mut self) {
|
||||
self.textarea.move_big_word_prev(); // 🎯 Direct FormEditor method call!
|
||||
self.update_debug_for_movement("B: previous WORD");
|
||||
}
|
||||
|
||||
fn move_big_word_end(&mut self) {
|
||||
self.textarea.move_big_word_end(); // 🎯 Direct FormEditor method call!
|
||||
self.update_debug_for_movement("E: WORD end");
|
||||
}
|
||||
|
||||
fn move_big_word_end_prev(&mut self) {
|
||||
self.textarea.move_big_word_end_prev(); // 🎯 Direct FormEditor method call!
|
||||
self.update_debug_for_movement("gE: previous WORD end");
|
||||
}
|
||||
|
||||
fn update_debug_for_movement(&mut self, action: &str) {
|
||||
self.debug_message = action.to_string();
|
||||
}
|
||||
@@ -214,15 +223,39 @@ Press ? for help, F1/F2 for manual cursor control demo.";
|
||||
// === DELETE OPERATIONS ===
|
||||
|
||||
fn delete_char_forward(&mut self) {
|
||||
self.send_key_to_textarea(KeyCode::Delete, KeyModifiers::NONE);
|
||||
self.has_unsaved_changes = true;
|
||||
self.debug_message = "x: deleted character".to_string();
|
||||
if let Ok(_) = self.textarea.delete_forward() { // 🎯 Direct FormEditor method call!
|
||||
self.has_unsaved_changes = true;
|
||||
self.debug_message = "x: deleted character".to_string();
|
||||
}
|
||||
}
|
||||
|
||||
fn delete_char_backward(&mut self) {
|
||||
self.send_key_to_textarea(KeyCode::Backspace, KeyModifiers::NONE);
|
||||
self.has_unsaved_changes = true;
|
||||
self.debug_message = "X: deleted character backward".to_string();
|
||||
if let Ok(_) = self.textarea.delete_backward() { // 🎯 Direct FormEditor method call!
|
||||
self.has_unsaved_changes = true;
|
||||
self.debug_message = "X: deleted character backward".to_string();
|
||||
}
|
||||
}
|
||||
|
||||
// === VIM-STYLE EDITING ===
|
||||
|
||||
fn open_line_below(&mut self) -> anyhow::Result<()> {
|
||||
let result = self.textarea.open_line_below(); // 🎯 Textarea-specific override!
|
||||
if result.is_ok() {
|
||||
CursorManager::update_for_mode(AppMode::Edit)?;
|
||||
self.debug_message = "✏️ INSERT (open line below) - Cursor: Steady Bar |".to_string();
|
||||
self.has_unsaved_changes = true;
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn open_line_above(&mut self) -> anyhow::Result<()> {
|
||||
let result = self.textarea.open_line_above(); // 🎯 Textarea-specific override!
|
||||
if result.is_ok() {
|
||||
CursorManager::update_for_mode(AppMode::Edit)?;
|
||||
self.debug_message = "✏️ INSERT (open line above) - Cursor: Steady Bar |".to_string();
|
||||
self.has_unsaved_changes = true;
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
// === COMMAND BUFFER HANDLING ===
|
||||
@@ -246,7 +279,7 @@ Press ? for help, F1/F2 for manual cursor control demo.";
|
||||
// === GETTERS ===
|
||||
|
||||
fn mode(&self) -> AppMode {
|
||||
self.mode
|
||||
self.textarea.mode() // 🎯 Direct FormEditor method call!
|
||||
}
|
||||
|
||||
fn debug_message(&self) -> &str {
|
||||
@@ -262,8 +295,11 @@ Press ? for help, F1/F2 for manual cursor control demo.";
|
||||
}
|
||||
|
||||
fn get_cursor_info(&self) -> String {
|
||||
// Since we can't access the internal editor, we'll provide a simpler status
|
||||
format!("Textarea cursor positioned")
|
||||
format!(
|
||||
"Line {}, Col {}",
|
||||
self.textarea.current_field() + 1, // 🎯 Direct FormEditor method call!
|
||||
self.textarea.cursor_position() + 1 // 🎯 Direct FormEditor method call!
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,6 +335,20 @@ fn handle_key_press(
|
||||
editor.clear_command_buffer();
|
||||
}
|
||||
|
||||
// Vim o/O commands
|
||||
(AppMode::ReadOnly, KeyCode::Char('o'), _) => {
|
||||
if let Err(e) = editor.open_line_below() {
|
||||
editor.set_debug_message(format!("Error opening line below: {}", e));
|
||||
}
|
||||
editor.clear_command_buffer();
|
||||
}
|
||||
(AppMode::ReadOnly, KeyCode::Char('O'), _) => {
|
||||
if let Err(e) = editor.open_line_above() {
|
||||
editor.set_debug_message(format!("Error opening line above: {}", e));
|
||||
}
|
||||
editor.clear_command_buffer();
|
||||
}
|
||||
|
||||
// Escape: Exit any mode back to normal
|
||||
(AppMode::Edit, KeyCode::Esc, _) => {
|
||||
editor.exit_to_normal_mode()?;
|
||||
@@ -350,9 +400,7 @@ fn handle_key_press(
|
||||
}
|
||||
(AppMode::ReadOnly, KeyCode::Char('e'), _) => {
|
||||
if editor.get_command_buffer() == "g" {
|
||||
// TODO: Implement ge (previous word end)
|
||||
editor.move_word_prev();
|
||||
editor.set_debug_message("ge: previous word end (simplified)".to_string());
|
||||
editor.move_word_end_prev();
|
||||
editor.clear_command_buffer();
|
||||
} else {
|
||||
editor.move_word_end();
|
||||
@@ -360,6 +408,25 @@ fn handle_key_press(
|
||||
}
|
||||
}
|
||||
|
||||
// Big word movement (vim W/B/E commands)
|
||||
(AppMode::ReadOnly, KeyCode::Char('W'), _) => {
|
||||
editor.move_big_word_next();
|
||||
editor.clear_command_buffer();
|
||||
}
|
||||
(AppMode::ReadOnly, KeyCode::Char('B'), _) => {
|
||||
editor.move_big_word_prev();
|
||||
editor.clear_command_buffer();
|
||||
}
|
||||
(AppMode::ReadOnly, KeyCode::Char('E'), _) => {
|
||||
if editor.get_command_buffer() == "g" {
|
||||
editor.move_big_word_end_prev();
|
||||
editor.clear_command_buffer();
|
||||
} else {
|
||||
editor.move_big_word_end();
|
||||
editor.clear_command_buffer();
|
||||
}
|
||||
}
|
||||
|
||||
// Line movement
|
||||
(AppMode::ReadOnly, KeyCode::Char('0'), _)
|
||||
| (AppMode::ReadOnly, KeyCode::Home, _) => {
|
||||
@@ -468,7 +535,7 @@ fn render_textarea(
|
||||
.title("🎯 Textarea with Automatic Cursor Management");
|
||||
|
||||
let textarea_widget = TextArea::default().block(block.clone());
|
||||
|
||||
|
||||
f.render_stateful_widget(textarea_widget, area, &mut editor.textarea);
|
||||
|
||||
// Set cursor position for terminal cursor
|
||||
@@ -518,8 +585,8 @@ fn render_status_and_help(
|
||||
}
|
||||
} else {
|
||||
"🎯 CURSOR-STYLE DEMO: Normal █ | Insert | \n\
|
||||
Normal: hjkl/arrows=move, w/b/e=words, 0/$=line, g/G=first/last\n\
|
||||
i/a/A=insert, x/X=delete, ?=info\n\
|
||||
Normal: hjkl/arrows=move, w/b/e=words, W/B/E=WORDS, 0/$=line, g/G=first/last\n\
|
||||
i/a/A/o/O=insert, x/X=delete, ?=info\n\
|
||||
F1=demo manual cursor, F2=restore automatic, Ctrl+Q=quit"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +90,27 @@ impl TextAreaProvider {
|
||||
self.lines[prev_idx].push_str(&curr);
|
||||
Some((prev_idx, prev_len))
|
||||
}
|
||||
|
||||
pub fn insert_blank_line_after(&mut self, idx: usize) -> usize {
|
||||
let clamped = idx.min(self.lines.len());
|
||||
let insert_at = if clamped >= self.lines.len() {
|
||||
self.lines.len()
|
||||
} else {
|
||||
clamped + 1
|
||||
};
|
||||
if insert_at == self.lines.len() {
|
||||
self.lines.push(String::new());
|
||||
} else {
|
||||
self.lines.insert(insert_at, String::new());
|
||||
}
|
||||
insert_at
|
||||
}
|
||||
|
||||
pub fn insert_blank_line_before(&mut self, idx: usize) -> usize {
|
||||
let insert_at = idx.min(self.lines.len());
|
||||
self.lines.insert(insert_at, String::new());
|
||||
insert_at
|
||||
}
|
||||
}
|
||||
|
||||
impl DataProvider for TextAreaProvider {
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
// src/textarea/state.rs
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use anyhow::Result;
|
||||
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
|
||||
|
||||
use crate::editor::FormEditor;
|
||||
use crate::textarea::provider::TextAreaProvider;
|
||||
|
||||
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
|
||||
|
||||
#[cfg(feature = "gui")]
|
||||
use ratatui::{layout::Rect, widgets::Block};
|
||||
|
||||
@@ -30,6 +33,21 @@ impl Default for TextAreaState {
|
||||
}
|
||||
}
|
||||
|
||||
// Expose the entire FormEditor API directly on TextAreaState
|
||||
impl Deref for TextAreaState {
|
||||
type Target = TextAreaEditor;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.editor
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for TextAreaState {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.editor
|
||||
}
|
||||
}
|
||||
|
||||
impl TextAreaState {
|
||||
pub fn from_text<S: Into<String>>(text: S) -> Self {
|
||||
let provider = TextAreaProvider::from_text(text);
|
||||
@@ -47,7 +65,6 @@ impl TextAreaState {
|
||||
|
||||
pub fn set_text<S: Into<String>>(&mut self, text: S) {
|
||||
self.editor.data_provider_mut().set_text(text);
|
||||
// Reset to first line and col 0
|
||||
self.editor.ui_state.current_field = 0;
|
||||
self.editor.ui_state.cursor_pos = 0;
|
||||
self.editor.ui_state.ideal_cursor_column = 0;
|
||||
@@ -61,29 +78,30 @@ impl TextAreaState {
|
||||
self.placeholder = Some(s.into());
|
||||
}
|
||||
|
||||
// Editing primitives specific to multi-line buffer
|
||||
// Textarea-specific primitive: split at cursor
|
||||
pub fn insert_newline(&mut self) {
|
||||
let line_idx = self.editor.current_field();
|
||||
let col = self.editor.cursor_position();
|
||||
let line_idx = self.current_field();
|
||||
let col = self.cursor_position();
|
||||
|
||||
let new_idx = self
|
||||
.editor
|
||||
.data_provider_mut()
|
||||
.split_line_at(line_idx, col);
|
||||
|
||||
let _ = self.editor.transition_to_field(new_idx);
|
||||
self.editor.move_line_start();
|
||||
self.editor.enter_edit_mode();
|
||||
let _ = self.transition_to_field(new_idx);
|
||||
self.move_line_start();
|
||||
self.enter_edit_mode();
|
||||
}
|
||||
|
||||
// Textarea-specific primitive: backspace with line join at start-of-line
|
||||
pub fn backspace(&mut self) {
|
||||
let col = self.editor.cursor_position();
|
||||
let col = self.cursor_position();
|
||||
if col > 0 {
|
||||
let _ = self.editor.delete_backward();
|
||||
let _ = self.delete_backward();
|
||||
return;
|
||||
}
|
||||
|
||||
let line_idx = self.editor.current_field();
|
||||
let line_idx = self.current_field();
|
||||
if line_idx == 0 {
|
||||
return;
|
||||
}
|
||||
@@ -93,19 +111,20 @@ impl TextAreaState {
|
||||
.data_provider_mut()
|
||||
.join_with_prev(line_idx)
|
||||
{
|
||||
let _ = self.editor.transition_to_field(prev_idx);
|
||||
self.editor.set_cursor_position(new_col);
|
||||
self.editor.enter_edit_mode();
|
||||
let _ = self.transition_to_field(prev_idx);
|
||||
self.set_cursor_position(new_col);
|
||||
self.enter_edit_mode();
|
||||
}
|
||||
}
|
||||
|
||||
// Textarea-specific primitive: delete or join with next line at EOL
|
||||
pub fn delete_forward_or_join(&mut self) {
|
||||
let line_idx = self.editor.current_field();
|
||||
let line_len = self.editor.current_text().chars().count();
|
||||
let col = self.editor.cursor_position();
|
||||
let line_idx = self.current_field();
|
||||
let line_len = self.current_text().chars().count();
|
||||
let col = self.cursor_position();
|
||||
|
||||
if col < line_len {
|
||||
let _ = self.editor.delete_forward();
|
||||
let _ = self.delete_forward();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -114,12 +133,40 @@ impl TextAreaState {
|
||||
.data_provider_mut()
|
||||
.join_with_next(line_idx)
|
||||
{
|
||||
self.editor.set_cursor_position(new_col);
|
||||
self.editor.enter_edit_mode();
|
||||
self.set_cursor_position(new_col);
|
||||
self.enter_edit_mode();
|
||||
}
|
||||
}
|
||||
|
||||
// Drive the editor from key events
|
||||
// Override for multiline: insert new blank line below and enter insert mode.
|
||||
pub fn open_line_below(&mut self) -> Result<()> {
|
||||
let line_idx = self.current_field();
|
||||
let new_idx = self
|
||||
.editor
|
||||
.data_provider_mut()
|
||||
.insert_blank_line_after(line_idx);
|
||||
|
||||
self.transition_to_field(new_idx)?;
|
||||
self.move_line_start();
|
||||
self.enter_edit_mode();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Override for multiline: insert new blank line above and enter insert mode.
|
||||
pub fn open_line_above(&mut self) -> Result<()> {
|
||||
let line_idx = self.current_field();
|
||||
let new_idx = self
|
||||
.editor
|
||||
.data_provider_mut()
|
||||
.insert_blank_line_before(line_idx);
|
||||
|
||||
self.transition_to_field(new_idx)?;
|
||||
self.move_line_start();
|
||||
self.enter_edit_mode();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Drive from KeyEvent; you can still call all FormEditor methods directly
|
||||
pub fn input(&mut self, key: KeyEvent) {
|
||||
if key.kind != KeyEventKind::Press {
|
||||
return;
|
||||
@@ -131,49 +178,43 @@ impl TextAreaState {
|
||||
(KeyCode::Delete, _) => self.delete_forward_or_join(),
|
||||
|
||||
(KeyCode::Left, _) => {
|
||||
let _ = self.editor.move_left();
|
||||
let _ = self.move_left();
|
||||
}
|
||||
(KeyCode::Right, _) => {
|
||||
let _ = self.editor.move_right();
|
||||
let _ = self.move_right();
|
||||
}
|
||||
(KeyCode::Up, _) => {
|
||||
let _ = self.editor.move_up();
|
||||
let _ = self.move_up();
|
||||
}
|
||||
(KeyCode::Down, _) => {
|
||||
let _ = self.editor.move_down();
|
||||
let _ = self.move_down();
|
||||
}
|
||||
|
||||
(KeyCode::Home, _)
|
||||
| (KeyCode::Char('a'), KeyModifiers::CONTROL) => {
|
||||
self.editor.move_line_start();
|
||||
self.move_line_start();
|
||||
}
|
||||
(KeyCode::End, _)
|
||||
| (KeyCode::Char('e'), KeyModifiers::CONTROL) => {
|
||||
self.editor.move_line_end();
|
||||
self.move_line_end();
|
||||
}
|
||||
|
||||
// Optional: word motions
|
||||
(KeyCode::Char('b'), KeyModifiers::ALT) => {
|
||||
self.editor.move_word_prev();
|
||||
}
|
||||
(KeyCode::Char('f'), KeyModifiers::ALT) => {
|
||||
self.editor.move_word_next();
|
||||
}
|
||||
(KeyCode::Char('e'), KeyModifiers::ALT) => {
|
||||
self.editor.move_word_end();
|
||||
}
|
||||
(KeyCode::Char('b'), KeyModifiers::ALT) => self.move_word_prev(),
|
||||
(KeyCode::Char('f'), KeyModifiers::ALT) => self.move_word_next(),
|
||||
(KeyCode::Char('e'), KeyModifiers::ALT) => self.move_word_end(),
|
||||
|
||||
// Insert printable characters
|
||||
// Printable characters
|
||||
(KeyCode::Char(c), m) if m.is_empty() => {
|
||||
self.editor.enter_edit_mode();
|
||||
let _ = self.editor.insert_char(c);
|
||||
self.enter_edit_mode();
|
||||
let _ = self.insert_char(c);
|
||||
}
|
||||
|
||||
// Tab: insert 4 spaces (simple default)
|
||||
// Simple Tab policy
|
||||
(KeyCode::Tab, _) => {
|
||||
self.editor.enter_edit_mode();
|
||||
self.enter_edit_mode();
|
||||
for _ in 0..4 {
|
||||
let _ = self.editor.insert_char(' ');
|
||||
let _ = self.insert_char(' ');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,20 +226,19 @@ impl TextAreaState {
|
||||
#[cfg(feature = "gui")]
|
||||
pub fn cursor(&self, area: Rect, block: Option<&Block<'_>>) -> (u16, u16) {
|
||||
let inner = if let Some(b) = block { b.inner(area) } else { area };
|
||||
let line_idx = self.editor.current_field() as u16;
|
||||
let line_idx = self.current_field() as u16;
|
||||
let y = inner.y + line_idx.saturating_sub(self.scroll_y);
|
||||
|
||||
let current_line = self.editor.current_text();
|
||||
let col = self.editor.display_cursor_position();
|
||||
let current_line = self.current_text();
|
||||
let col = self.display_cursor_position();
|
||||
|
||||
let mut x_off: u16 = 0;
|
||||
for (i, ch) in current_line.chars().enumerate() {
|
||||
if i >= col {
|
||||
break;
|
||||
}
|
||||
x_off = x_off.saturating_add(
|
||||
UnicodeWidthChar::width(ch).unwrap_or(0) as u16,
|
||||
);
|
||||
x_off = x_off
|
||||
.saturating_add(UnicodeWidthChar::width(ch).unwrap_or(0) as u16);
|
||||
}
|
||||
let x = inner.x.saturating_add(x_off);
|
||||
(x, y)
|
||||
@@ -214,7 +254,7 @@ impl TextAreaState {
|
||||
if inner.height == 0 {
|
||||
return;
|
||||
}
|
||||
let line_idx = self.editor.current_field() as u16;
|
||||
let line_idx = self.current_field() as u16;
|
||||
if line_idx < self.scroll_y {
|
||||
self.scroll_y = line_idx;
|
||||
} else if line_idx >= self.scroll_y + inner.height {
|
||||
|
||||
Reference in New Issue
Block a user