v0.0.18: proper line editing in TUI input
Keyboard shortcuts: - Left/Right: move cursor - Home / Ctrl+A: beginning of line - End / Ctrl+E: end of line - Alt+Left/Right: word jump - Alt+Backspace: delete word back - Ctrl+W: delete word back - Ctrl+U: clear entire line - Ctrl+K: kill to end of line - Delete: delete char at cursor - Backspace: delete char before cursor Cursor position tracked, chars insert at cursor (not just append). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.0.17"
|
||||
version = "0.0.18"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
rust-version = "1.75"
|
||||
|
||||
@@ -52,6 +52,7 @@ pub struct App {
|
||||
pub peer_fp: Option<String>,
|
||||
pub server_url: String,
|
||||
pub should_quit: bool,
|
||||
pub cursor_pos: usize,
|
||||
pub last_dm_peer: Arc<Mutex<Option<String>>>,
|
||||
/// Track receipt status for messages we sent, keyed by message ID.
|
||||
pub receipts: Arc<Mutex<HashMap<String, ReceiptStatus>>>,
|
||||
@@ -113,6 +114,7 @@ impl App {
|
||||
server_url,
|
||||
should_quit: false,
|
||||
last_dm_peer: Arc::new(Mutex::new(None)),
|
||||
cursor_pos: 0,
|
||||
receipts: Arc::new(Mutex::new(HashMap::new())),
|
||||
pending_files: Arc::new(Mutex::new(HashMap::new())),
|
||||
}
|
||||
@@ -227,7 +229,7 @@ impl App {
|
||||
frame.render_widget(input_widget, chunks[2]);
|
||||
|
||||
// Cursor
|
||||
let x = (self.input.len() as u16 + 1).min(chunks[2].width - 2);
|
||||
let x = (self.cursor_pos as u16 + 1).min(chunks[2].width - 2);
|
||||
frame.set_cursor_position((chunks[2].x + x, chunks[2].y + 1));
|
||||
}
|
||||
|
||||
@@ -239,6 +241,7 @@ impl App {
|
||||
) {
|
||||
let text = self.input.trim().to_string();
|
||||
self.input.clear();
|
||||
self.cursor_pos = 0;
|
||||
|
||||
if text.is_empty() {
|
||||
return;
|
||||
@@ -1520,11 +1523,78 @@ pub async fn run_tui(
|
||||
KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||
app.should_quit = true;
|
||||
}
|
||||
KeyCode::Backspace => {
|
||||
app.input.pop();
|
||||
// Alt+Backspace / Ctrl+W: delete word before cursor
|
||||
KeyCode::Backspace if key.modifiers.contains(KeyModifiers::ALT) => {
|
||||
if app.cursor_pos > 0 {
|
||||
let before = &app.input[..app.cursor_pos];
|
||||
let new_pos = before.trim_end().rfind(' ').map(|i| i + 1).unwrap_or(0);
|
||||
app.input.drain(new_pos..app.cursor_pos);
|
||||
app.cursor_pos = new_pos;
|
||||
}
|
||||
}
|
||||
// Backspace: delete char before cursor
|
||||
KeyCode::Backspace => {
|
||||
if app.cursor_pos > 0 {
|
||||
app.input.remove(app.cursor_pos - 1);
|
||||
app.cursor_pos -= 1;
|
||||
}
|
||||
}
|
||||
// Delete: delete char at cursor
|
||||
KeyCode::Delete => {
|
||||
if app.cursor_pos < app.input.len() {
|
||||
app.input.remove(app.cursor_pos);
|
||||
}
|
||||
}
|
||||
// Left arrow
|
||||
KeyCode::Left => {
|
||||
if key.modifiers.contains(KeyModifiers::ALT) {
|
||||
// Alt+Left: word left
|
||||
let before = &app.input[..app.cursor_pos];
|
||||
app.cursor_pos = before.rfind(' ').map(|i| i).unwrap_or(0);
|
||||
} else if app.cursor_pos > 0 {
|
||||
app.cursor_pos -= 1;
|
||||
}
|
||||
}
|
||||
// Right arrow
|
||||
KeyCode::Right => {
|
||||
if key.modifiers.contains(KeyModifiers::ALT) {
|
||||
// Alt+Right: word right
|
||||
let after = &app.input[app.cursor_pos..];
|
||||
app.cursor_pos += after.find(' ').map(|i| i + 1).unwrap_or(after.len());
|
||||
} else if app.cursor_pos < app.input.len() {
|
||||
app.cursor_pos += 1;
|
||||
}
|
||||
}
|
||||
// Home / Ctrl+A
|
||||
KeyCode::Home => { app.cursor_pos = 0; }
|
||||
KeyCode::Char('a') if key.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||
app.cursor_pos = 0;
|
||||
}
|
||||
// End / Ctrl+E
|
||||
KeyCode::End => { app.cursor_pos = app.input.len(); }
|
||||
KeyCode::Char('e') if key.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||
app.cursor_pos = app.input.len();
|
||||
}
|
||||
// Ctrl+U: clear line
|
||||
KeyCode::Char('u') if key.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||
app.input.clear();
|
||||
app.cursor_pos = 0;
|
||||
}
|
||||
// Ctrl+K: kill to end of line
|
||||
KeyCode::Char('k') if key.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||
app.input.truncate(app.cursor_pos);
|
||||
}
|
||||
// Ctrl+W: delete word back
|
||||
KeyCode::Char('w') if key.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||
let before = &app.input[..app.cursor_pos];
|
||||
let new_pos = before.trim_end().rfind(' ').map(|i| i + 1).unwrap_or(0);
|
||||
app.input.drain(new_pos..app.cursor_pos);
|
||||
app.cursor_pos = new_pos;
|
||||
}
|
||||
// Regular char: insert at cursor
|
||||
KeyCode::Char(c) => {
|
||||
app.input.push(c);
|
||||
app.input.insert(app.cursor_pos, c);
|
||||
app.cursor_pos += 1;
|
||||
}
|
||||
KeyCode::Esc => {
|
||||
app.should_quit = true;
|
||||
|
||||
Reference in New Issue
Block a user