如何覆盖控制台输出?

11

有没有一种使用Rust覆盖控制台输出而不是简单地附加的方法?

一个示例是将进度打印为百分比;我宁愿覆盖该行而不是打印新行。

2个回答

16

通常通过打印“控制字符”来控制控制台,但这取决于平台和终端类型。你可能不想重新发明轮子来做这个。

你可以使用crossterm crate来获取这种控制台控制。一个简单的例子是:

use std::{thread, time};
use std::io::{Write, stdout};
use crossterm::{QueueableCommand, cursor, terminal, ExecutableCommand};

fn main() {
    let mut stdout = stdout();

    stdout.execute(cursor::Hide).unwrap();
    for i in (1..30).rev() {
        stdout.queue(cursor::SavePosition).unwrap();
        stdout.write_all(format!("{}: FOOBAR ", i).as_bytes()).unwrap();
        stdout.queue(cursor::RestorePosition).unwrap();
        stdout.flush().unwrap();
        thread::sleep(time::Duration::from_millis(100));

        stdout.queue(cursor::RestorePosition).unwrap();
        stdout.queue(terminal::Clear(terminal::ClearType::FromCursorDown)).unwrap();
    }
    stdout.execute(cursor::Show).unwrap();

    println!("Done!");
}

回到这个问题,需要注意的是它实际上并没有清除掉它所写的控制台行,而只是在其上面写入了新的内容。例如,先写入 started 然后再写入 done 将会留下 doneted。有没有一种方法可以清除行呢?(现在正在查看 crossterm::terminal::Clear,但是没有示例很难理解) - Jonathan Woollett-light
看起来要清除当前行,只需排队一个 terminal::Clear(ClearType::CurrentLine) - Michael Anderson
1
如果您正在尝试组合更复杂的文本GUI,则crossterm可能处于错误的级别 - 您最好使用TUI(https://github.com/fdehau/tui-rs)。 - Michael Anderson
1
SavePosition/RestorePosition 对我无效,但 MoveToPreviousLine(n) 有效。此代码还存在编译器警告,因为没有处理任何 Results。 - piegames
@piegames 你是否添加了错误处理来解决 SavePosition / RestorePosition 调用无法正常工作的问题?只需在每个 queue 调用后添加 .unwrap() 可能就足以得到一些想法。 (它们对我肯定有效,我认为这可能取决于您的终端功能/类型。) - Michael Anderson
Michael Anderson: 不,我添加了错误处理是因为整个代码在IDE中都是黄色的。 :D 此外,添加了错误“处理”后,代码不会出现崩溃,所以不是那个问题。可能是因为我写了多行,但手动移动光标也可以正常工作,所以我没有费心去调查。 - piegames

7

如果采用底层方法,你可以使用\r转义字符使光标返回到行首并从那里开始覆盖。

use std::{
    io::{stdout, Write},
    thread::sleep,
    time::Duration,
};

fn main() {
    let mut stdout = stdout();

    for i in 0..=100 {
        print!("\rProcessing {}%...", i);
        // or
        // stdout.write(format!("\rProcessing {}%...", i).as_bytes()).unwrap();

        stdout.flush().unwrap();
        sleep(Duration::from_millis(20));
    }
    println!();
}

您也可以使用退格字符将光标向后移动一个位置。

const BACKSPACE: char = 8u8 as char;

print!("{}\rThis replaces the previous line", BACKSPACE);

注意:

这种方法最适用于像这样简单的递增值。如果您写入Hello, World!然后是\rbar, 您将得到baro, World!。如果您想要清除输出,最好的方法是跟踪您已经写入的字符数,并用空格覆盖它们,或者您可以使用一个可以给您终端大小的库。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接