1use std::borrow::Cow;
2use std::time::{Duration, Instant};
3
4use crate::confirm::{Answer, Confirm, ConfirmBar, Words};
5use crate::key::Key;
6
7pub const WINDOW: Duration = Duration::from_secs(2);
8
9#[derive(Debug, Clone, PartialEq, Eq)]
10#[non_exhaustive]
11pub struct Wording {
12 pub again: Cow<'static, str>,
13 pub ask: Cow<'static, str>,
14}
15
16impl Wording {
17 pub fn new(again: impl Into<Cow<'static, str>>) -> Self {
18 Wording {
19 again: again.into(),
20 ask: Cow::Borrowed(""),
21 }
22 }
23
24 pub fn ask(mut self, ask: impl Into<Cow<'static, str>>) -> Self {
25 self.ask = ask.into();
26 self
27 }
28}
29
30#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
31#[non_exhaustive]
32pub enum Mode {
33 #[default]
34 Twice,
35 Ask,
36 Once,
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40#[non_exhaustive]
41pub enum Guard {
42 Pass,
43 Held,
44 Quit,
45}
46
47#[derive(Debug, Clone, PartialEq, Eq)]
48pub struct QuitGuard {
49 mode: Mode,
50 window: Duration,
51 triggers: &'static [Key],
52 armed: Option<Instant>,
53 asking: Option<Confirm>,
54 confirm: Confirm,
55 wording: Wording,
56}
57
58impl QuitGuard {
59 pub fn new(wording: Wording) -> Self {
60 QuitGuard {
61 mode: Mode::Twice,
62 window: WINDOW,
63 triggers: &[Key::Ctrl('c')],
64 armed: None,
65 asking: None,
66 confirm: Confirm::new(),
67 wording,
68 }
69 }
70
71 pub fn mode(mut self, mode: Mode) -> Self {
72 self.mode = mode;
73 self
74 }
75
76 pub fn window(mut self, window: Duration) -> Self {
77 self.window = window;
78 self
79 }
80
81 pub fn triggers(mut self, triggers: &'static [Key]) -> Self {
82 self.triggers = triggers;
83 self
84 }
85
86 pub fn confirm(mut self, confirm: Confirm) -> Self {
87 self.confirm = confirm;
88 self
89 }
90
91 pub fn wording_mut(&mut self) -> &mut Wording {
92 &mut self.wording
93 }
94
95 pub fn key(&mut self, key: Key, now: Instant, busy: bool) -> Guard {
96 let trigger = self.triggers.contains(&key);
97 if let Some(confirm) = self.asking.as_mut() {
98 let answer = if trigger {
99 Some(Answer::No)
100 } else {
101 confirm.key(key)
102 };
103 return match answer {
104 Some(Answer::Yes) => {
105 self.asking = None;
106 Guard::Quit
107 }
108 Some(Answer::No) => {
109 self.asking = None;
110 Guard::Held
111 }
112 None => Guard::Held,
113 };
114 }
115 if !trigger {
116 self.armed = None;
117 return Guard::Pass;
118 }
119 if self.mode == Mode::Once {
120 return Guard::Quit;
121 }
122 let again = self
123 .armed
124 .take()
125 .is_some_and(|at| now.saturating_duration_since(at) < self.window);
126 if !again {
127 self.armed = Some(now);
128 return Guard::Held;
129 }
130 if self.mode == Mode::Ask && busy {
131 self.asking = Some(self.confirm);
132 return Guard::Held;
133 }
134 Guard::Quit
135 }
136
137 pub fn tick(&mut self, now: Instant) -> bool {
138 if self
139 .armed
140 .is_some_and(|at| now.saturating_duration_since(at) >= self.window)
141 {
142 self.armed = None;
143 return true;
144 }
145 false
146 }
147
148 pub fn deadline(&self) -> Option<Instant> {
149 self.armed.and_then(|at| at.checked_add(self.window))
150 }
151
152 pub fn armed(&self) -> bool {
153 self.armed.is_some()
154 }
155
156 pub fn notice(&self) -> Option<&str> {
157 self.armed.map(|_| self.wording.again.as_ref())
158 }
159
160 pub fn asking(&self) -> Option<&Confirm> {
161 self.asking.as_ref()
162 }
163
164 pub fn bar<'a>(&'a self, words: Words<'a>) -> Option<ConfirmBar<'a>> {
165 self.asking
166 .as_ref()
167 .map(|confirm| ConfirmBar::new(&self.wording.ask, confirm, words))
168 }
169
170 pub fn cancel(&mut self) {
171 self.armed = None;
172 self.asking = None;
173 }
174}