Fix unlocked TUI visibility and clipboard feedback
This commit is contained in:
@@ -3,18 +3,17 @@
|
||||
use std::{cell::Cell, fmt};
|
||||
|
||||
use ironstorage::{
|
||||
document::{DocumentError, EntryDocument, EntryField, EntryFieldId, EntrySensitivity},
|
||||
document::{DocumentError, EntryDocument, EntryField},
|
||||
repository::SecretBytes,
|
||||
};
|
||||
|
||||
/// Focus, masking, and scrolling state for an authenticated document.
|
||||
/// Focus and scrolling state for an authenticated document.
|
||||
///
|
||||
/// The document remains the source of field order, labels, kinds, sensitivity,
|
||||
/// and values. This type only tracks transient presentation choices.
|
||||
pub struct EntryViewer {
|
||||
document: EntryDocument,
|
||||
focused: usize,
|
||||
revealed: Option<EntryFieldId>,
|
||||
scroll: Cell<usize>,
|
||||
}
|
||||
|
||||
@@ -23,7 +22,6 @@ impl EntryViewer {
|
||||
Self {
|
||||
document,
|
||||
focused: 0,
|
||||
revealed: None,
|
||||
scroll: Cell::new(0),
|
||||
}
|
||||
}
|
||||
@@ -44,19 +42,13 @@ impl EntryViewer {
|
||||
self.document.fields().get(self.focused)
|
||||
}
|
||||
|
||||
pub fn is_revealed(&self, id: EntryFieldId) -> bool {
|
||||
self.revealed == Some(id)
|
||||
}
|
||||
|
||||
pub fn focus_next(&mut self) {
|
||||
self.hide_revealed();
|
||||
if !self.document.fields().is_empty() {
|
||||
self.focused = (self.focused + 1) % self.document.fields().len();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn focus_previous(&mut self) {
|
||||
self.hide_revealed();
|
||||
if !self.document.fields().is_empty() {
|
||||
self.focused = self
|
||||
.focused
|
||||
@@ -65,21 +57,6 @@ impl EntryViewer {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reveal_focused(&mut self) -> bool {
|
||||
let Some(field) = self.focused_field() else {
|
||||
return false;
|
||||
};
|
||||
if field.metadata().sensitivity() != EntrySensitivity::Sensitive {
|
||||
return false;
|
||||
}
|
||||
self.revealed = Some(field.id());
|
||||
true
|
||||
}
|
||||
|
||||
pub fn hide_revealed(&mut self) -> bool {
|
||||
self.revealed.take().is_some()
|
||||
}
|
||||
|
||||
pub fn copy_focused(&self) -> Result<SecretBytes, DocumentError> {
|
||||
let field = self.focused_field().ok_or(DocumentError::InvalidIndex {
|
||||
index: self.focused,
|
||||
@@ -147,7 +124,6 @@ impl fmt::Debug for EntryViewer {
|
||||
.debug_struct("EntryViewer")
|
||||
.field("document", &self.document)
|
||||
.field("focused", &self.focused)
|
||||
.field("revealed", &self.revealed.map(|_| "[REDACTED]"))
|
||||
.field("scroll", &self.scroll.get())
|
||||
.finish()
|
||||
}
|
||||
@@ -158,8 +134,10 @@ pub(crate) mod test_support {
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use ironstorage::{
|
||||
command::GrepRequest,
|
||||
crypto::{KeyInfo, KeyStore, SecretProvider, SecretProviderError},
|
||||
document::{EntryDocument, EntryDocumentService},
|
||||
read::{GrepResults, VaultReader},
|
||||
repository::{EntryPath, Repository, SecretBytes},
|
||||
};
|
||||
use tempfile::TempDir;
|
||||
@@ -183,6 +161,25 @@ pub(crate) mod test_support {
|
||||
fixture_document_from(&root.join("stores/basic"), entry)
|
||||
}
|
||||
|
||||
pub(crate) fn fixture_grep() -> GrepResults {
|
||||
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../../crates/storage/tests/fixtures/compatibility");
|
||||
let repository = Repository::open(root.join("stores/basic")).expect("fixture repository");
|
||||
let keys = KeyStore::load(root.join("keys")).expect("fixture keys");
|
||||
VaultReader::new(&repository, &keys)
|
||||
.grep(
|
||||
&GrepRequest {
|
||||
pattern: "alice".to_owned(),
|
||||
ignore_case: false,
|
||||
invert_match: false,
|
||||
line_number: true,
|
||||
fixed_strings: true,
|
||||
},
|
||||
&mut FixtureSecrets,
|
||||
)
|
||||
.expect("fixture grep")
|
||||
}
|
||||
|
||||
pub(crate) fn fixture_document_from(store: &Path, entry: &str) -> EntryDocument {
|
||||
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../../crates/storage/tests/fixtures/compatibility");
|
||||
@@ -224,14 +221,9 @@ mod tests {
|
||||
use super::{test_support::fixture_document, *};
|
||||
|
||||
#[test]
|
||||
fn focus_wraps_and_always_hides_a_revealed_secret() {
|
||||
fn focus_wraps_without_changing_the_authenticated_document() {
|
||||
let mut viewer = EntryViewer::new(fixture_document("email/personal"));
|
||||
assert!(viewer.reveal_focused());
|
||||
let password = viewer.focused_field().expect("password").id();
|
||||
assert!(viewer.is_revealed(password));
|
||||
|
||||
viewer.focus_next();
|
||||
assert!(!viewer.is_revealed(password));
|
||||
assert_eq!(viewer.focused_index(), Some(1));
|
||||
viewer.focus_previous();
|
||||
assert_eq!(viewer.focused_index(), Some(0));
|
||||
@@ -247,10 +239,9 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ordinary_fields_do_not_gain_reveal_state_and_scroll_saturates() {
|
||||
fn scrolling_saturates() {
|
||||
let mut viewer = EntryViewer::new(fixture_document("unicode/咖啡"));
|
||||
viewer.focus_next();
|
||||
assert!(!viewer.reveal_focused());
|
||||
viewer.scroll_down(usize::MAX);
|
||||
let end = viewer.scroll();
|
||||
viewer.scroll_down(1);
|
||||
|
||||
Reference in New Issue
Block a user