use std::time::{Duration, Instant};
use super::fuzzy::FuzzyList;
use super::{App, Command, Modal, Screen};
use crate::keymap::Action;
const HOLD: Duration = Duration::from_millis(500);
#[derive(Debug, Clone, Copy)]
pub(crate) struct HeldPress {
col: u16,
row: u16,
since: Instant,
}
impl HeldPress {
pub(crate) fn new(col: u16, row: u16) -> Self {
Self {
col,
row,
since: Instant::now(),
}
}
}
impl App {
pub(super) fn open_context_menu(&mut self, col: u16, row: u16, select: bool) {
if select && !self.visual_active() {
self.select_at(col, row);
}
let wanted = self.menu_actions();
let index = self.command_index();
let commands: Vec<Command> = wanted
.iter()
.filter_map(|action| index.iter().find(|command| command.action == *action))
.cloned()
.collect();
if commands.is_empty() {
return;
}
let labels: Vec<String> = commands
.iter()
.map(|command| command.label.to_owned())
.collect();
let mut list = FuzzyList::default();
list.rerank(&labels);
self.modal = Some(Modal::Menu { commands, list });
}
pub(super) fn check_held_press(&mut self) -> bool {
let Some(held) = self.held_press else {
return false;
};
if held.since.elapsed() < HOLD {
return false;
}
self.held_press = None;
if self.modal.is_some() || self.composer_open() {
return false;
}
self.open_context_menu(held.col, held.row, false);
true
}
fn menu_actions(&self) -> Vec<Action> {
match self.screen() {
Screen::Diff => self.diff_menu_actions(),
Screen::Status => self.status_menu_actions(),
Screen::Log | Screen::CiLog => {
vec![Action::Open, Action::CopyUrl, Action::CopyFileFeedback]
}
Screen::Prs | Screen::Runs | Screen::Graph | Screen::File | Screen::Stats => Vec::new(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::LoadedConfig;
use crate::event::AppEvent;
use crate::test_support::{Fixture, render};
#[test]
fn a_press_held_still_opens_the_menu() {
let fixture = Fixture::new();
fixture.write("a.txt", "one\n");
fixture.commit_all("base");
fixture.write("a.txt", "two\n");
let mut app = App::new(fixture.review(), LoadedConfig::default());
app.open_working_tree_diff(None);
render(&mut app);
let pane = app.diff.as_ref().expect("diff").pane;
let mut held = HeldPress::new(pane.x + 6, pane.y + 1);
held.since = Instant::now()
.checked_sub(HOLD * 2)
.expect("a past instant");
app.held_press = Some(held);
app.handle(AppEvent::Tick);
assert!(matches!(app.modal, Some(Modal::Menu { .. })));
}
}