You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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();
}

问题根源

  1. 未跟踪光标位置:现有代码没有记录光标在输入字符串中的位置,无法处理中间位置的删除操作。
  2. 错误的字符删除逻辑:input.pop()仅能删除字符串末尾的字符,光标在中间时,需要删除对应位置的字符而非末尾。
  3. 未重新绘制完整输入:删除中间字符后,没有重新绘制整个输入内容,导致右侧字符无法左移填补空位。

解决方案

关键修改点

  • 新增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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 03:23:27