rust set cursor position for user input

Viewed 165

I'm a newbie rust programmer and I'm writing a program that prints a table, and using a loop, gets the numeric values from the user and each time the user enters the value, the table gets updated and printed again until all the values are received.

            class1      class2      class3      class4      class5
Sensor      0           0           0           0           0
visual      0           0           0           0           0

I want to put the cursor at the desired cell (and blink if possible) which the user is going to enter the value for. Which means at first iteration the cursor should be at the cell sensor-class1, 2nd iteration at the sensor-class2 and so on.

I searched for a while and seems that termion is the solution, but print!("{}", termion::cursor::Goto((10 * i).try_into().unwrap(), j)); shifts the printed table in each iteration and not the user input cursor:

extern crate termion;
use std::io;

fn main() {
    let mut sensor_data = [0; 5];
    let mut visual_data = [0; 5];

    for j in 1..3 {
        for i in 1..6 {
            print!("{}", termion::clear::All);
            print!("{}", termion::cursor::Goto(1, 1));

            println!(
                "{0: <10}  {1: <10}  {2: <10}  {3: <10}  {4: <10}  {5: <10}",
                "", "class1", "class2", "class3", "class4", "class5"
            );
            println!(
                "{0: <10}  {1: <10}  {2: <10}  {3: <10}  {4: <10}  {5: <10}",
                "Sensor",
                sensor_data[0],
                sensor_data[1],
                sensor_data[2],
                sensor_data[3],
                sensor_data[4]
            );
            println!(
                "{0: <10}  {1: <10}  {2: <10}  {3: <10}  {4: <10}  {5: <10}",
                "visual",
                visual_data[0],
                visual_data[1],
                visual_data[2],
                visual_data[3],
                visual_data[4]
            );

            //update the cursor position for user input

            let mut input = String::new();
            io::stdin()
                .read_line(&mut input)
                .expect("Failed to read line");
            if j == 1 {sensor_data[i - 1] = input.trim().parse().expect("Please type a number!");}
            else {visual_data[i - 1] = input.trim().parse().expect("Please type a number!");}
        }
    }
// print the complete table and go on
}


1 Answers

I think primarily, you are missing a stdout().flush().

print!(), contrary to println!(), does not write a newline character. On most systems a newline character causes a flush, but as there is no newline character, the Goto gets stuck in the write buffer.

An easy fix would be to flush() manually, as shown in the code sample below.

The more proper way, though, would be to access the terminal in raw mode to disable the buffer in the first place.

use std::io::{self, Write};

fn main() {
    let mut sensor_data = [0; 5];
    let mut visual_data = [0; 5];

    for j in 1..3 {
        for i in 1..6 {
            print!("{}", termion::clear::All);
            print!("{}", termion::cursor::Goto(1, 1));

            println!(
                "{0: <10}  {1: <10}  {2: <10}  {3: <10}  {4: <10}  {5: <10}",
                "", "class1", "class2", "class3", "class4", "class5"
            );
            println!(
                "{0: <10}  {1: <10}  {2: <10}  {3: <10}  {4: <10}  {5: <10}",
                "Sensor",
                sensor_data[0],
                sensor_data[1],
                sensor_data[2],
                sensor_data[3],
                sensor_data[4]
            );
            println!(
                "{0: <10}  {1: <10}  {2: <10}  {3: <10}  {4: <10}  {5: <10}",
                "visual",
                visual_data[0],
                visual_data[1],
                visual_data[2],
                visual_data[3],
                visual_data[4]
            );

            //update the cursor position for user input
            print!("{}", termion::cursor::Goto(12 * i + 1, j + 1));
            io::stdout().flush().unwrap();

            let mut input = String::new();
            io::stdin()
                .read_line(&mut input)
                .expect("Failed to read line");
            if j == 1 {
                sensor_data[i as usize - 1] = input.trim().parse().expect("Please type a number!");
            } else {
                visual_data[i as usize - 1] = input.trim().parse().expect("Please type a number!");
            }
        }
    }
    // print the complete table and go on
}

Other minor fixes I added:

  • Remove extern crate as it is no longer required or advised
  • Change type of i to u16 and convert to usize instead of the other way round, to avoid the unwrap()
Related