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]
|
[workspace.package]
|
||||||
version = "0.0.17"
|
version = "0.0.18"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
rust-version = "1.75"
|
rust-version = "1.75"
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ pub struct App {
|
|||||||
pub peer_fp: Option<String>,
|
pub peer_fp: Option<String>,
|
||||||
pub server_url: String,
|
pub server_url: String,
|
||||||
pub should_quit: bool,
|
pub should_quit: bool,
|
||||||
|
pub cursor_pos: usize,
|
||||||
pub last_dm_peer: Arc<Mutex<Option<String>>>,
|
pub last_dm_peer: Arc<Mutex<Option<String>>>,
|
||||||
/// Track receipt status for messages we sent, keyed by message ID.
|
/// Track receipt status for messages we sent, keyed by message ID.
|
||||||
pub receipts: Arc<Mutex<HashMap<String, ReceiptStatus>>>,
|
pub receipts: Arc<Mutex<HashMap<String, ReceiptStatus>>>,
|
||||||
@@ -113,6 +114,7 @@ impl App {
|
|||||||
server_url,
|
server_url,
|
||||||
should_quit: false,
|
should_quit: false,
|
||||||
last_dm_peer: Arc::new(Mutex::new(None)),
|
last_dm_peer: Arc::new(Mutex::new(None)),
|
||||||
|
cursor_pos: 0,
|
||||||
receipts: Arc::new(Mutex::new(HashMap::new())),
|
receipts: Arc::new(Mutex::new(HashMap::new())),
|
||||||
pending_files: 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]);
|
frame.render_widget(input_widget, chunks[2]);
|
||||||
|
|
||||||
// Cursor
|
// 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));
|
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();
|
let text = self.input.trim().to_string();
|
||||||
self.input.clear();
|
self.input.clear();
|
||||||
|
self.cursor_pos = 0;
|
||||||
|
|
||||||
if text.is_empty() {
|
if text.is_empty() {
|
||||||
return;
|
return;
|
||||||
@@ -1520,11 +1523,78 @@ pub async fn run_tui(
|
|||||||
KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
|
KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||||
app.should_quit = true;
|
app.should_quit = true;
|
||||||
}
|
}
|
||||||
KeyCode::Backspace => {
|
// Alt+Backspace / Ctrl+W: delete word before cursor
|
||||||
app.input.pop();
|
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) => {
|
KeyCode::Char(c) => {
|
||||||
app.input.push(c);
|
app.input.insert(app.cursor_pos, c);
|
||||||
|
app.cursor_pos += 1;
|
||||||
}
|
}
|
||||||
KeyCode::Esc => {
|
KeyCode::Esc => {
|
||||||
app.should_quit = true;
|
app.should_quit = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user