use std::sync::{Arc, Mutex};
pub use crate::session::PromptKind;
#[derive(Clone)]
pub struct SecretPromptHandle {
tx: tokio::sync::mpsc::UnboundedSender<SecretPromptRequest>,
}
impl SecretPromptHandle {
pub fn new(tx: tokio::sync::mpsc::UnboundedSender<SecretPromptRequest>) -> Self {
Self { tx }
}
pub async fn prompt(&self, title: String, prompt: String) -> Option<String> {
let (response_tx, response_rx) = tokio::sync::oneshot::channel();
let request = SecretPromptRequest {
kind: PromptKind::from_title(&title),
title,
prompt,
response_tx,
};
self.tx.send(request).ok()?;
response_rx.await.ok().flatten()
}
}
pub struct SecretPromptRequest {
pub kind: PromptKind,
pub title: String,
pub prompt: String,
pub response_tx: tokio::sync::oneshot::Sender<Option<String>>,
}
pub struct PendingSecretPrompt {
pub kind: PromptKind,
pub title: String,
pub prompt: String,
pub buffer: String,
pub response_tx: tokio::sync::oneshot::Sender<Option<String>>,
}
pub struct SecretPromptQueue {
active: Option<PendingSecretPrompt>,
pending: std::collections::VecDeque<SecretPromptRequest>,
}
impl Default for SecretPromptQueue {
fn default() -> Self {
Self::new()
}
}
impl SecretPromptQueue {
pub fn new() -> Self {
Self {
active: None,
pending: std::collections::VecDeque::new(),
}
}
pub fn poll_requests(
&mut self,
rx: &Arc<Mutex<tokio::sync::mpsc::UnboundedReceiver<SecretPromptRequest>>>,
) {
if let Ok(mut rx) = rx.lock() {
while let Ok(req) = rx.try_recv() {
self.pending.push_back(req);
}
}
self.activate_next();
}
fn activate_next(&mut self) {
if self.active.is_some() {
return;
}
if let Some(req) = self.pending.pop_front() {
self.active = Some(PendingSecretPrompt {
kind: req.kind,
title: req.title,
prompt: req.prompt,
buffer: String::new(),
response_tx: req.response_tx,
});
}
}
pub fn is_active(&self) -> bool {
self.active.is_some()
}
pub fn active(&self) -> Option<&PendingSecretPrompt> {
self.active.as_ref()
}
pub fn push_char(&mut self, ch: char) {
if let Some(active) = self.active.as_mut() {
active.buffer.push(ch);
}
}
pub fn backspace(&mut self) {
if let Some(active) = self.active.as_mut() {
active.buffer.pop();
}
}
pub fn submit(&mut self) {
if let Some(mut active) = self.active.take() {
let secret = std::mem::take(&mut active.buffer);
let _ = active.response_tx.send(Some(secret));
}
self.activate_next();
}
pub fn cancel(&mut self) {
if let Some(mut active) = self.active.take() {
active.buffer.clear();
let _ = active.response_tx.send(None);
}
self.activate_next();
}
pub fn dismiss(&mut self) {
if let Some(mut active) = self.active.take() {
active.buffer.clear();
drop(active.response_tx);
}
self.activate_next();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dismiss_drops_without_answering_and_activates_next() {
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
let rx = Arc::new(Mutex::new(rx));
let (tx1, mut rx1) = tokio::sync::oneshot::channel();
let (tx2, mut rx2) = tokio::sync::oneshot::channel();
tx.send(SecretPromptRequest { kind: PromptKind::Secret, title: "a".into(), prompt: "p".into(), response_tx: tx1 }).unwrap();
tx.send(SecretPromptRequest { kind: PromptKind::Secret, title: "b".into(), prompt: "p".into(), response_tx: tx2 }).unwrap();
let mut q = SecretPromptQueue::new();
q.poll_requests(&rx);
assert_eq!(q.active().unwrap().title, "a");
q.push_char('z');
q.dismiss();
assert!(matches!(rx1.try_recv(), Err(tokio::sync::oneshot::error::TryRecvError::Closed)));
assert_eq!(q.active().unwrap().title, "b");
assert_eq!(q.active().unwrap().buffer, "");
q.push_char('x');
q.submit();
assert_eq!(rx2.try_recv().unwrap(), Some("x".to_string()));
assert!(!q.is_active());
}
}