Skip to main content

strop_engine/editor/remote/
view.rs

1//! A remote view's content contract is distinct from file identity and placement.
2use strop_remote::{ReadLimit, ReadSelection, RemoteOffset};
3
4#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
5pub enum RemoteView {
6    Snapshot(ReadSelection),
7    Follow(ReadLimit),
8}
9impl Default for RemoteView {
10    fn default() -> Self {
11        Self::Snapshot(ReadSelection::Full)
12    }
13}
14impl RemoteView {
15    pub fn selection(&self) -> ReadSelection {
16        match self {
17            Self::Snapshot(selection) => *selection,
18            Self::Follow(limit) => ReadSelection::Tail(*limit),
19        }
20    }
21    pub fn follow_limit(&self) -> Option<ReadLimit> {
22        match self {
23            Self::Follow(limit) => Some(*limit),
24            Self::Snapshot(_) => None,
25        }
26    }
27}
28
29#[derive(Debug, thiserror::Error)]
30pub enum ViewError {
31    #[error("byte counts must be nonnegative decimal integers")]
32    Number,
33    #[error("a range is START:BYTES")]
34    Range,
35    #[error(transparent)]
36    Limit(#[from] strop_remote::ReadLimitError),
37}
38pub(crate) fn offset(text: &str) -> Result<RemoteOffset, ViewError> {
39    Ok(RemoteOffset::new(number(text)?))
40}
41pub fn limit(text: &str) -> Result<ReadLimit, ViewError> {
42    Ok(ReadLimit::new(number(text)?)?)
43}
44pub fn range(text: &str) -> Result<ReadSelection, ViewError> {
45    let (start, length) = text.split_once(':').ok_or(ViewError::Range)?;
46    Ok(ReadSelection::Range {
47        start: offset(start)?,
48        length: limit(length)?,
49    })
50}
51fn number(text: &str) -> Result<u64, ViewError> {
52    if text.is_empty() || !text.bytes().all(|byte| byte.is_ascii_digit()) {
53        return Err(ViewError::Number);
54    }
55    text.parse().map_err(|_| ViewError::Number)
56}