为何使用AlternateScreen读取输入按键时需按下Enter?
我使用Rust语言的termion crate开发了一个简单的命令行工具,按下任意按键时会打印“Hello world!”,输入字符q则退出程序。以下是使用AlternateScreen结构体在终端输出的代码:
use std::io::{stdin, stdout, Write}; use termion::event::Key; use termion::input::TermRead; use termion::raw::IntoRawMode; use termion::screen::IntoAlternateScreen; fn main() { // Initialize stdout and stdin let stdin = stdin(); let mut stdout = stdout().into_alternate_screen().unwrap(); // Print a message indicating what to do write!( stdout, "{}{}Press any key to print 'Hello world!', or press 'q' to quit.{}", termion::clear::All, termion::cursor::Goto(1, 1), termion::cursor::Hide ) .unwrap(); stdout.flush().unwrap(); // Create an input reader for key events let mut row = 2; for c in stdin.keys() { let key = c.unwrap(); if key == Key::Char('q') { break; } write!( stdout, "{}{}Hello world!", termion::cursor::Goto(1, row), termion::clear::CurrentLine ) .unwrap(); stdout.flush().unwrap(); row += 1; } // Enable back the cursor write!(stdout, "{}", termion::cursor::Show).unwrap(); }
我通过定位到下一行首列来写入新消息,row变量记录下一条消息的写入位置。问题在于,我必须按下Enter才能让标准输入识别字符,且额外的Enter键会导致多输出一条“Hello world!”。我希望按下任意输入键后立即写入文本,无需按Enter。
注意,若将标准输出替换为RawTerminal结构体,一切都能正常工作:
let mut stdout = stdout().into_raw_mode().unwrap();
此时输入字符无需按下Enter。请问为何使用AlternateScreen读取输入按键时需要按下Enter?
核心原因是:AlternateScreen仅负责切换终端的显示屏幕,不会修改终端的输入模式,而你需要的“按键立即响应”依赖于终端的原始模式(raw mode)。
终端默认处于规范模式(canonical mode),这种模式下输入会被系统缓冲,直到按下Enter才会把整行数据传递给程序,所以你必须按Enter才能触发输入处理,且Enter本身也会被当作一个按键事件,导致多输出一条消息。
而IntoRawMode的作用就是将终端切换到原始模式,这种模式下系统不会缓冲输入,每个按键都会被立即传递给程序,无需等待Enter。你用RawTerminal能正常工作,就是因为它直接启用了原始模式。
解决方法很简单,只需要在获取AlternateScreen后,再将其转换为原始模式即可,修改初始化stdout的代码:
let mut stdout = stdout().into_alternate_screen().unwrap().into_raw_mode().unwrap();
这样既保留了备用屏幕的功能,又让终端进入原始模式,按键就能被立即捕获,无需按Enter。
内容的提问来源于stack exchange,提问作者Péter Szilvási

