Set a duration on event

Hi, I'm currently in process of building a TUI. I need to know how would you realize something like time related key stroke tracker. I wish I could add some cool features, such as hitting 'c' twice and it would pop a new layout. I found the std::time that lets you use Duration::time() to set time on something. Do I need to take time snaps or am I overthinking it? Thanks!

You are not overthinking it, but you only need a tiny state machine.

For a TUI, track keystrokes with std::time::Instant, not Duration::time().

Use this idea:

use std::time::{Duration, Instant};

struct KeyTracker {
    last_key: Option<char>,
    last_time: Option<Instant>,
}

impl KeyTracker {
    fn new() -> Self {
        Self {
            last_key: None,
            last_time: None,
        }
    }

    fn press(&mut self, key: char) -> bool {
        let now = Instant::now();
        let double_tap_window = Duration::from_millis(400);

        let is_double_tap =
            self.last_key == Some(key)
                && self
                    .last_time
                    .map(|t| now.duration_since(t) <= double_tap_window)
                    .unwrap_or(false);

        self.last_key = Some(key);
        self.last_time = Some(now);

        is_double_tap
    }
}

Then in your event loop:

let mut keys = KeyTracker::new();

loop {
    if let Event::Key(event) = crossterm::event::read()? {
        match event.code {
            KeyCode::Char('c') => {
                if keys.press('c') {
                    // c pressed twice quickly
                    app.show_new_layout();
                }
            }

            KeyCode::Char('q') => break,

            _ => {}
        }
    }
}

The important bit:

Instant::now()

You take a timestamp when a key is pressed, then compare the next press against it.

Use Instant for measuring elapsed time. Use SystemTime only for calendar/wall-clock time. For keyboard timing, Instant is the right spell.

For more complex combos later, store a small buffer:

Vec<(char, Instant)>

Then you can support:

  • c c -> open layout
  • g g -> top
  • d d -> delete line
  • ctrl+x c -> command

Your mental model is right: snap time on each input event, compare with previous snap. That’s all.

you have two options:

  • store the history of key events, at least the recent ones, and tag each event with a timestamp. how many recent events to store depending on the input patterns of your use case.

    this may need more work, but it could support more sophisticated input patterns, like multi-key chords (e.g jk or kj pressed together as a single command).

  • in the event loop, set a timeout when reading inputs, and translate the timeout as a special kind of input event (a pseduo key, if you will). then you can use this special timeout event to define key bindings.

    for example, ctrl - x, <Timeout> can be easily differentiated from ctrl - x, ctrl - x; or similarly, the c, <Timeout> is a single click, while c, c is a double click. (this can also be extended to support tripple click if you need to).

thanks much!!!