How to get keyboard input with Termion in Rust? - rust

I'm trying out Rust and I really like it so far. I'm working on a tool that needs to get arrow key input from the user. So far, I've got something half-working: if I hold a key for a while, the relevant function gets called. However, it's far from instantaneous.
What I've got so far:
let mut stdout = io::stdout().into_raw_mode();
let mut stdin = termion::async_stdin();
// let mut stdin = io::stdin();
let mut it = stdin.keys(); //iterator object
loop {
//copied straight from GitLab: https://gitlab.redox-os.org/redox-os/termion/-/issues/168
let b = it.next();
match b {
Some(x) => match x {
Ok(k) => {
match k {
Key::Left => move_cursor(&mut cursor_char, -1, &enc_chars, &mpt, &status),
Key::Right => move_cursor(&mut cursor_char, 1, &enc_chars, &mpt, &status),
Key::Ctrl('c') => break,
_ => {}
}
},
_ => {}
},
None => {}
}
//this loop might do nothing if no recognized key was pressed.
}
I don't quite understand it myself. I'm using the terminal raw mode, if that has anything to do with it. I've looked at the rustyline crate, but that's really no good as it's more of an interactive shell-thing, and I just want to detect keypresses.

If you're using raw input mode and reading key by key, you'll need to manually buffer the character keys using the same kind of match loop you already have. The Key::Char(ch) enum variant can be used to match regular characters. You can then use either a mutable String or an array like [u8; MAX_SIZE] to store the character data and append characters as they're typed. If the user moves the cursor, you'd need to keep track of the current position within your input buffer and make sure to insert the newly typed characters into the correct spot, moving the existing characters if needed. It is a lot of work, which is why there are crates that will do it for you, but you will have less chance to control how the input behaves. If you want to use an existing crate, then tui-rs might be a good one to check out for a complete solution, or linefeed for something much simpler.
As for the delay, I think it might be because you're using AsyncReader, which according to the docs is using a secondary thread to do blocking reads

Related

get() function doesn't yield any result on tuple keys, and the values exist and do match when manually checked

let mut FEED_CA_:HashMap<(Address, Address, Address), (iuniswapv2pair_mod::IUniswapV2Pair<Provider<Ws>>, iuniswapv2pair_mod::IUniswapV2Pair<Provider<Ws>>, iuniswapv2pair_mod::IUniswapV2Pair<Provider<Ws>>, iuniswapv2pair_mod::IUniswapV2Pair<Provider<Ws>>, iuniswapv2pair_mod::IUniswapV2Pair<Provider<Ws>>, iuniswapv2pair_mod::IUniswapV2Pair<Provider<Ws>>)> = HashMap::new();
for i in FEED_CA_START{
let c0 = i[0];
let c1 = i[1];
let c2 = i[2];
let p_01_ = ALL_C.get(&&(*c0, *c1)).ok_or(continue).unwrap();
FEED_CA_START is a vec of (c0, c1, c2) tuples :)
ALL_C is a HashMap of <(Address, Address), Address> from which I can't get the value out.
The whole iteration fails on last line in the above snippet. There's eight more of those get()s, and not one of them gets filled, the output data structure (FEED_CA_ ) to which I insert results after iterating through the whole db is always len = 0.
I've:
Printed out the values &(c0, c1) for each i in my tuple FEED_CA_START
Printed out the values of the faulty &(c0, c1), as well as &(*c0, *c1) and even &&(*c0, *c1).
Manually found that they indeed do match, do exist (100% of those calls should succeed).
Printed out the types of the above and made sure they indeed do match. Same result, even if types of the query and key match and are both exactly: &(primitive_types::H160, primitive_types::H160)
Done some borrow&deref trial-and-error combinations, there's only so many I can try, and still no hope.
printed out the databases I've derived those from and manually checked - there's multiple matches, and the program returns exactly 0 always, and I've tried multiple functions/methods/iterations, my program always breaks at get() function, goes straight to continue.
I ran a simple test where in a new program I've created a HashMap with addresses akin to ALL_C , and accessed it using a tuple key - and there it worked. Here it doesn't. I've even thrown some reference/typing spaghetti there to make sure it breaks as well, it didn't. Got the value every time. Here I'm trying as hard as I can to make it work, and it's day 2 stuck on this issue, dead end.
How do I approach a problem like that? I'm lost.
If I've understood the code snippet is correctly, I think your problem is the .ok_or(continue).
Arguments passed to ok_or are eagerly evaluated; if you are passing the result of a function call, it is recommended to use ok_or_else, which is lazily evaluated.
In other words, the continue is always evaluated and so the code is really not doing what you expect.
let p_01_ = ALL_C.get(&(c0, c1)).ok_or(continue).unwrap();
println!("unreachable");
As a possible solution, you could use filter_map which will ignore all None results and then check the length. It doesn't seem very elegant but it might be better that repeated if let blocks:
// Note: I just guessed some types for an example
for i in FEED_CA_START {
let c0 = i.0;
let c1 = i.1;
let c2 = i.2;
let args = vec![(c0, c1), (c0, c2)];
let ps: Vec<&Address> = args.iter().filter_map(|arg| ALL_C.get(arg)).collect();
if ps.len() != args.len() {
continue;
}
// Do something
}
As per the other answer, continue is evaluated so it never get to check the other code. IMO it should be imposible to construct that in the first place.
On the other hand, it is not a good construct to do that, you should use something more rusty like:
let mut FEED_CA_:HashMap<(Address, Address, Address), (iuniswapv2pair_mod::IUniswapV2Pair<Provider<Ws>>, iuniswapv2pair_mod::IUniswapV2Pair<Provider<Ws>>, iuniswapv2pair_mod::IUniswapV2Pair<Provider<Ws>>, iuniswapv2pair_mod::IUniswapV2Pair<Provider<Ws>>, iuniswapv2pair_mod::IUniswapV2Pair<Provider<Ws>>, iuniswapv2pair_mod::IUniswapV2Pair<Provider<Ws>>)> = HashMap::new();
for i in FEED_CA_START{
let c0 = i[0];
let c1 = i[1];
let c2 = i[2];
if let Some(p_01_) = ALL_C.get(&&(*c0, *c1)) {
// compute stuff over `p_01_`
} else { // else could be avoided if you don't need to compute anything else in the loop if there is nothing for the key
continue
};
...

How to reduce flicker in terminal re-drawing?

I have a program that displays the state of some commands ran in parallel
fmt ✔
clippy cargo clippy --tests --color always ...
tests cargo test --color always ..
The program is my first one that relies on multi-threading, and I have some threads running those programs as soon as they are "available", and I have one thread (the main one) dedicated to waiting for new results (which are pretty rare, given that jobs tend to run for at leat a few seconds, and there a relatively few jobs, 10 in parallel at most) and deleting & reprinting in a loop the state of things.
In this part of the software, I don't print the output of the commands, just the commands being ran and some ascii spinner.
I don't know how these things should be done, so I managed to limit redraws to at least 40ms :
const AWAIT_TIME: Duration = std::time::Duration::from_millis(40);
fn delay(&mut self) -> usize {
let time_for = AWAIT_TIME
- SystemTime::now()
.duration_since(self.last_occurence)
.unwrap();
let millis: usize = std::cmp::max(time_for.as_millis() as usize, 0);
if millis != 0 {
sleep(time_for);
}
self.last_occurence = SystemTime::now();
millis
}
while let Some(progress) = read(&rx) { ... }
job_display.refresh(&tracker, delay);
delay = job_starter.delay();
So I end up tracking the number of lines and chars written and delete them all :
struct TermWrapper {
term: Box<StdoutTerminal>,
written_lines: u16,
written_chars: usize,
}
...
pub fn clear(&mut self) {
(0..self.written_lines as usize).for_each(|_| {
self.term.cursor_up().unwrap();
self.term.carriage_return().unwrap();
self.term.delete_line().unwrap();
});
self.written_lines = 0;
self.written_chars = 0;
}
It works, but it tends to flicker, especially in embedded terminals.
My next idea is to store the hash of printed string and skip the redraw if I can.
Are there some known patterns I can apply to get some nicer output ?
What are the common strategies I can use ?
The minimum requirement to guarantee no flicker when updating a terminal is: don't send one thing and then overwrite it with something else (within a single 'frame' of drawing). In the case of clearing, we can restate that rule more specifically: don't clear the regions that you're going to put text in. Instead, clear only regions that you know you aren't putting text in (in case there is previous text there).
The conventional terminal command set contains a very useful tool for this: the “clear to end of line” command. The way you can use it is:
Move the cursor to the beginning of a line you want to replace the text in.
Write the text, without any newline or CRLF at the end
Write “clear to end of line”. (In crossterm, that's ClearType::UntilNewLine.)
After sending the clear command, the rest of the line is cleared (just as if you had happened to write the exact number of spaces to completely fill the line). In this way, you need to keep track of which lines you're writing on, but you don't need to keep track of the exact width of each string you wrote.
The next step beyond this, useful for arbitrary 2D screen layouts, is to remember what text has previously been sent to the terminal, and only send what needs to be changed — in Rust, the tui crate provides this, and you can also find bindings to the well-known C library curses for the same purpose.

How to animate the terminal in Rust

I need to profile several variables like frames per second being rendered in my app. Therefore I need a simple way to update variables in the terminal.
I've searched and found ascii_table for generating tables, and termion for updating the terminal. But I suspect termion here is simply being used to clear the terminal.
Anyways, I was able to draw a simple table and update its contents every 200 miliseconds:
use ascii_table::{Align, AsciiTable, Column};
extern crate termion;
use termion::{clear, color, cursor};
use std::fmt::Display;
use std::{thread, time};
fn main() {
let mut ascii_table = AsciiTable::default();
ascii_table.max_width = 40;
let mut column = Column::default();
column.header = "H1".into();
column.align = Align::Left;
ascii_table.columns.insert(0, column);
let mut column = Column::default();
column.header = "H2".into();
column.align = Align::Center;
ascii_table.columns.insert(1, column);
let mut column = Column::default();
column.header = "H3".into();
column.align = Align::Right;
ascii_table.columns.insert(2, column);
let mut i = 0;
while (true) {
let data: Vec<Vec<&dyn Display>> = vec![
vec![&i, &"hello", &789],
];
let s = ascii_table.format(data.clone());
println!(
"\n{}{}{}{}",
cursor::Hide,
clear::All,
cursor::Goto(1, 1),
s
);
println!("Hello");//couldn't make this appear on top.
i = i+1;
std::thread::sleep(std::time::Duration::from_millis(200));
}
}
Is this the way programs like top update data on the terminal? Or is there a better way? It'd be nice to have more complex structures.
There isn't a fundamentally better way to format complex data on a terminal than you're doing. There are some individual refinements that can be made to improve display quality.
In particular, in order to reduce flickering, it is best to overwrite text rather than clearing the entire terminal first, and only clear the parts that either need to become blank or are already blank, using narrower clear operations such as clear to end of line, which you would use when you're replacing a line and it might become shorter — by putting this clear at the end of the text, so that if the text is unchanged it doesn't disappear briefly.
Since you're starting with code that generates multiline text, you'll need to edit the string:
// Insert clear-to-end-of-line into the table
let s = ascii_table.format(data.clone())
.replace("\n", &format!("{}\n", clear::UntilNewline));
println!(
"\n{}{}{}{}",
cursor::Hide,
cursor::Goto(1, 1),
s,
clear::AfterCursor,
);
Notice that I have reordered the operations: first, we go to (1, 1) and draw the text (clearing to end of line as we go). Then when everything is done, we clear from cursor to end of screen. This way, we're never clearing any of the text we want to be still present, so there will be no flicker.
I notice you have another wish:
println!("Hello");//couldn't make this appear on top.
All you need to do here is do it after the goto and before the table, and include clearing, and it'll work as you'd like.
// Move cursor to top. This always goes first.
// Note print!, not println!, since we don't want to move down
// after the goto.
print!("{}{}", cursor::Hide, cursor::Goto(1, 1));
// Use clear::UntilNewline on intermediate things
println!("Hello{}", clear::UntilNewline);
// ...even if they are blank lines
println!("{}", clear::UntilNewline);
// Use clear::AfterCursor on the *last* thing printed
// Note print!, not println!, since if we are filling the entire terminal
// we don't want to cause it to scroll down.
print!("{}{}", s, clear::AfterCursor);
One thing I haven't covered is that you can also use termion::cursor::Goto to move to specific areas on the terminal to update them, instead of writing entire lines top-to-bottom. This is of course more complex since your program has to comprehend the entire layout to know what cursor position to go to, and know which parts need to be redrawn. In the days of actual serial terminals and modems that had very low data rates, this was a very important optimization to avoid wasting transmission time on characters that were the same — today, it's less critical.

Rust - nested loop going only once inside inner loop

I am taking data from text file
let fil1_json = File::open("fil1.json")?;
let mut fil1_json_reader = BufReader::new(fil1_json);
let fil2_json = File::open("fil2.json")?;
let mut fil2_json_reader = BufReader::new(fil2_json);
for fil1_line in fil1_json_reader.by_ref().lines() {
for fil2_line in fil2_json_reader.by_ref().lines() {
println!("{:#?} ----- {:#?}", fil1_line, fil2_line);
}
}
In the second nested loop, it is only going inside once. It looks like fil2_json_reader is getting emptied after first iteration.
Where it is changing as I am not changing anywhere?
Where it is changing as I am not changing anywhere?
Readers consume the data. In the case of File, this is the natural expectation, since file abstractions almost universally have a cursor that advances every time you read.
If you want to iterate several times over the same data, then the obvious option is saving it to memory (typically before splitting into lines(), but you can also save a vector of those even if it will be slower). However, since the reader is backed by an actual file, it is better to re-iterate over the file by seeking to its beginning:
fil2_json_reader.seek(SeekFrom::Start(0))

How using Rust do I assign the value being matched as the result?

I've used "match" a little in Rust, and I've searched for a solution to this problem, because I'm sure there must be a solution. In the following example, in the case where the random number generated is not zero, I want the random-number generated to be assigned to the variable i_bal. In other words, instead of assigning "23456" as per my example, I want to assign the actual random number being tested in the match. I know that there may be better ways of solving this, however, I'd like to know the solution using this method.
Example :
let i_bal: i64 = match std::rand::task_rng().gen() {
0 => 1234,
_ => 23456
};
Instead of the wildcard pattern you can use the variable pattern. They both match everything, but with the variable pattern the matched value is bound to the name:
let i_bal: i64 = match std::rand::task_rng().gen() {
0 => 1234,
x => x
};
Why in some cases you'd want to use the wildcard pattern is so that you make it clear you don't need the value, and so that you don't have to pollute the scope with unnecessary variable names
For example if you had a tuple of (high, low, open, close), and only wanted the variables high and low from it you could do:
let (high, low, _, _) = tickerData

Resources