在Termion原始模式下实现完整退格功能:解决右侧字符不左移问题
使用Termion实现带光标移动的退格键功能问题解决
问题描述
使用Termion库在原始模式下实现退格键功能时,删除末尾字符正常,但按下左箭头移动光标后删除字符,光标右侧的字符不会向左移动。现有代码如下:
use std::io::{Write, stdout, stdin}; use termion::input::TermRead; use termion::raw::IntoRawMode; use termion::{clear, cursor, Key}; fn main() { let stdin = stdin(); let mut stdout = stdout().into_raw_mode().unwrap(); write!(stdout, "{}{}q to exit. Type stuff, use alt, and so on.{}", clear::All, cursor::Goto(1, 1), cursor::Hide) .unwrap(); stdout.flush().unwrap(); let mut input = String::new(); for c in stdin.keys() { write!(stdout, "{}{}", cursor::Goto(1, 1), clear::CurrentLine) .unwrap(); match c.unwrap() { Key::Char('q') => break, Key::Char(c) => { input.push(c); write!(stdout, "{}", c).unwrap(); } Key::Backspace => { input.pop(); // This should be changed too write!(stdout, "{}", cursor::Left(1)).unwrap(); write!(stdout, " ").unwrap(); write!(stdout, "{}", cursor::Left(1)).unwrap(); } _ => {} } stdout.flush().unwrap(); } let input = input.trim().to_owned(); println!("Do something with the input: {}", input); write!(stdout, "{}", cursor::Show).unwrap(); }
问题根源
- 未跟踪光标位置:现有代码没有记录光标在输入字符串中的位置,无法处理中间位置的删除操作。
- 错误的字符删除逻辑:
input.pop()仅能删除字符串末尾的字符,光标在中间时,需要删除对应位置的字符而非末尾。 - 未重新绘制完整输入:删除中间字符后,没有重新绘制整个输入内容,导致右侧字符无法左移填补空位。
解决方案
关键修改点
- 新增
cursor_pos变量跟踪光标在输入字符串中的位置(Termion光标坐标从1开始)。 - 处理左箭头按键,调整光标位置。
- 修改退格键逻辑:删除光标位置的字符,重新绘制整个输入内容,再将光标移到删除后的位置。
- 处理字符插入:当光标不在末尾时,将字符插入到光标对应位置,而非直接追加到末尾。
修正后的完整代码
use std::io::{Write, stdout, stdin}; use termion::input::TermRead; use termion::raw::IntoRawMode; use termion::{clear, cursor, Key}; fn main() { let stdin = stdin(); let mut stdout = stdout().into_raw_mode().unwrap(); // 初始化屏幕提示 write!(stdout, "{}{}q to exit. Type stuff, use left arrow and backspace.{}", clear::All, cursor::Goto(1, 1), cursor::Hide) .unwrap(); stdout.flush().unwrap(); let mut input = String::new(); // 光标位置,初始在输入起始位置(对应字符串索引0,Termion坐标1) let mut cursor_pos = 1; for c in stdin.keys() { // 每次操作先清空当前行并回到行首 write!(stdout, "{}{}", cursor::Goto(1, 1), clear::CurrentLine).unwrap(); match c.unwrap() { Key::Char('q') => break, Key::Char(c) => { // 插入字符到光标对应的位置 let idx = (cursor_pos - 1) as usize; input.insert(idx, c); // 光标后移一位 cursor_pos += 1; // 重新绘制整个输入 write!(stdout, "{}", input).unwrap(); // 将光标移到正确位置 write!(stdout, "{}", cursor::Goto(cursor_pos, 1)).unwrap(); } Key::Backspace => { if cursor_pos > 1 { let idx = (cursor_pos - 1) as usize; // 删除光标左侧的字符(光标位置对应的前一个索引) input.remove(idx - 1); // 光标左移一位 cursor_pos -= 1; } // 重新绘制整个输入 write!(stdout, "{}", input).unwrap(); // 将光标移到正确位置 write!(stdout, "{}", cursor::Goto(cursor_pos, 1)).unwrap(); } Key::Left => { // 光标左移,不能小于1 if cursor_pos > 1 { cursor_pos -= 1; } // 重新绘制输入并定位光标 write!(stdout, "{}", input).unwrap(); write!(stdout, "{}", cursor::Goto(cursor_pos, 1)).unwrap(); } _ => {} } stdout.flush().unwrap(); } let input = input.trim().to_owned(); // 恢复终端模式并输出结果 write!(stdout, "{}{}", cursor::Show, clear::All).unwrap(); writeln!(stdout, "Do something with the input: {}", input).unwrap(); }
代码说明
- 光标位置跟踪:
cursor_pos变量记录当前光标在输入行的坐标(从1开始),对应字符串的索引为cursor_pos - 1。 - 字符插入:使用
input.insert(idx, c)将字符插入到光标对应的位置,而非追加到末尾。 - 退格处理:删除光标左侧的字符(通过
input.remove(idx - 1)),然后重新绘制整个输入内容,确保右侧字符左移填补空位。 - 光标移动:处理左箭头按键时,调整
cursor_pos并重新定位光标。
内容的提问来源于stack exchange,提问作者wyc
相关产品推荐
相关产品推荐

