Revamp desktop usability and multiline fields

This commit is contained in:
2026-08-10 22:06:16 +02:00
parent 40b15a9713
commit aeb18b488f
12 changed files with 1283 additions and 545 deletions

View File

@@ -1,17 +1,14 @@
//! Structured desktop editing state over storage-owned entry documents.
use std::{collections::BTreeSet, fmt};
use std::fmt;
use ironstorage::{
document::{
DocumentError, EntryDocument, EntryField, EntryFieldDraft, EntryFieldId, EntrySensitivity,
},
document::{DocumentError, EntryDocument, EntryField, EntryFieldDraft, EntryFieldId},
repository::SecretBytes,
};
pub struct EntryEditor {
document: EntryDocument,
revealed: BTreeSet<EntryFieldId>,
focused: Option<EntryFieldId>,
dirty: bool,
}
@@ -29,7 +26,6 @@ impl EntryEditor {
let focused = document.fields().first().map(EntryField::id);
Self {
document,
revealed: BTreeSet::new(),
focused,
dirty: false,
}
@@ -66,10 +62,6 @@ impl EntryEditor {
self.dirty
}
pub fn is_revealed(&self, id: EntryFieldId) -> bool {
self.revealed.contains(&id)
}
pub fn focused(&self) -> Option<EntryFieldId> {
self.focused
}
@@ -108,20 +100,7 @@ impl EntryEditor {
self.focused = Some(fields[index].id());
}
pub fn toggle_reveal(&mut self, id: EntryFieldId) -> Result<(), DocumentError> {
let field = self
.document
.field(id)
.ok_or(DocumentError::UnknownField { id })?;
if field.metadata().sensitivity() != EntrySensitivity::Sensitive {
return Ok(());
}
if !self.revealed.remove(&id) {
self.revealed.insert(id);
}
Ok(())
}
#[cfg(test)]
pub fn update_raw(&mut self, id: EntryFieldId, value: &[u8]) -> Result<(), DocumentError> {
let unchanged = self
.document
@@ -131,7 +110,44 @@ impl EntryEditor {
return Ok(());
}
self.document
.update(id, EntryFieldDraft::line(value.to_vec())?)?;
.update(id, EntryFieldDraft::multiline(value.to_vec()))?;
self.dirty = true;
Ok(())
}
pub fn update_value_line(
&mut self,
id: EntryFieldId,
line: usize,
replacement: &[u8],
) -> Result<(), DocumentError> {
let field = self
.document
.field(id)
.ok_or(DocumentError::UnknownField { id })?;
let mut value = field.value().to_vec();
let range =
value_line_range(&value, line).ok_or(DocumentError::InvalidIndex { index: line })?;
value.splice(range, replacement.iter().copied());
self.document.replace_field_value(id, value)?;
self.dirty = true;
Ok(())
}
pub fn add_value_line_after(
&mut self,
id: EntryFieldId,
line: usize,
) -> Result<(), DocumentError> {
let field = self
.document
.field(id)
.ok_or(DocumentError::UnknownField { id })?;
let mut value = field.value().to_vec();
let insertion =
value_line_end(&value, line).ok_or(DocumentError::InvalidIndex { index: line })?;
value.splice(insertion..insertion, *b"\n");
self.document.replace_field_value(id, value)?;
self.dirty = true;
Ok(())
}
@@ -158,7 +174,6 @@ impl EntryEditor {
.position(|field| field.id() == id)
.ok_or(DocumentError::UnknownField { id })?;
self.document.remove(id)?;
self.revealed.remove(&id);
if self.focused == Some(id) {
self.focused = self
.document
@@ -209,7 +224,6 @@ impl EntryEditor {
) -> Result<(), DocumentError> {
self.document
.replace_field_value(id, value.expose().to_vec())?;
self.revealed.remove(&id);
self.dirty = true;
Ok(())
}
@@ -219,12 +233,42 @@ impl EntryEditor {
}
}
fn value_line_range(value: &[u8], target: usize) -> Option<std::ops::Range<usize>> {
let mut start = 0;
let mut lines = 0;
for (line, segment) in value.split_inclusive(|byte| *byte == b'\n').enumerate() {
lines = line + 1;
let mut end = start + segment.len() - usize::from(segment.ends_with(b"\n"));
if end > start && value[end - 1] == b'\r' {
end -= 1;
}
if line == target {
return Some(start..end);
}
start += segment.len();
}
(target == lines && (value.is_empty() || value.ends_with(b"\n")))
.then_some(value.len()..value.len())
}
fn value_line_end(value: &[u8], target: usize) -> Option<usize> {
let mut end = 0;
let mut lines = 0;
for (line, segment) in value.split_inclusive(|byte| *byte == b'\n').enumerate() {
lines = line + 1;
end += segment.len();
if line == target {
return Some(end);
}
}
(target == lines && (value.is_empty() || value.ends_with(b"\n"))).then_some(value.len())
}
impl fmt::Debug for EntryEditor {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("EntryEditor")
.field("document", &self.document)
.field("revealed", &self.revealed)
.field("focused", &self.focused)
.field("dirty", &self.dirty)
.finish()