Skip to main content

markdown_editor/
selection.rs

1use super::{
2    document::EditorDocument,
3    id::BlockId,
4    position::{BlockPosition, PositionError},
5};
6
7#[derive(Clone, PartialEq, Debug)]
8pub enum BlockSelection {
9    /// Text range, possibly spanning blocks; `anchor == head` is a caret.
10    /// Atoms and code blocks between the endpoints count as fully covered.
11    Text {
12        anchor: BlockPosition,
13        head: BlockPosition,
14    },
15    /// Whole blocks as objects. Endpoints must be siblings.
16    Blocks { anchor: BlockId, head: BlockId },
17    /// A rectangle of table cells (both ends in the same table), from
18    /// dragging or Shift+arrows across cells. Copy gives a Markdown table;
19    /// delete clears the cells' contents.
20    Cells { anchor: BlockId, head: BlockId },
21}
22
23impl BlockSelection {
24    pub fn caret(position: BlockPosition) -> Self {
25        Self::Text {
26            anchor: position,
27            head: position,
28        }
29    }
30
31    pub fn is_collapsed(&self) -> bool {
32        matches!(self, Self::Text { anchor, head } if anchor == head)
33    }
34}
35
36#[non_exhaustive]
37#[derive(Clone, Copy, PartialEq, Eq, Debug)]
38pub enum SelectionError {
39    Position(PositionError),
40    UnknownBlock(BlockId),
41    NotSiblings,
42}
43
44impl From<PositionError> for SelectionError {
45    fn from(error: PositionError) -> Self {
46        Self::Position(error)
47    }
48}
49
50/// A text selection ordered in document order.
51#[derive(Clone, Copy, PartialEq, Debug)]
52pub(crate) struct ResolvedSelection {
53    pub(crate) start: BlockPosition,
54    pub(crate) end: BlockPosition,
55}
56
57impl ResolvedSelection {
58    pub(crate) fn is_collapsed(&self) -> bool {
59        self.start.block == self.end.block && self.start.offset == self.end.offset
60    }
61}
62
63impl EditorDocument {
64    pub(crate) fn validate_selection(
65        &self,
66        selection: &BlockSelection,
67    ) -> Result<(), SelectionError> {
68        match selection {
69            BlockSelection::Text { anchor, head } => {
70                self.validate(*anchor)?;
71                self.validate(*head)?;
72            }
73            BlockSelection::Blocks { anchor, head } => {
74                let a = self
75                    .get(*anchor)
76                    .ok_or(SelectionError::UnknownBlock(*anchor))?;
77                let b = self.get(*head).ok_or(SelectionError::UnknownBlock(*head))?;
78                if a.parent != b.parent || a.parent.is_none() {
79                    return Err(SelectionError::NotSiblings);
80                }
81            }
82            BlockSelection::Cells { anchor, head } => {
83                let table = |id: BlockId| super::table::table_of(self, id);
84                match (table(*anchor), table(*head)) {
85                    (Some(a), Some(b)) if a == b => {}
86                    (None, _) => return Err(SelectionError::UnknownBlock(*anchor)),
87                    _ => return Err(SelectionError::NotSiblings),
88                }
89            }
90        }
91        Ok(())
92    }
93
94    /// Resolve to an ordered text range. A block selection covers its blocks'
95    /// first to last caret.
96    pub(crate) fn resolve_selection(
97        &self,
98        selection: &BlockSelection,
99    ) -> Result<ResolvedSelection, SelectionError> {
100        self.validate_selection(selection)?;
101        let (a, b) = match selection {
102            BlockSelection::Text { anchor, head } => (*anchor, *head),
103            BlockSelection::Blocks { anchor, head } => {
104                let first = |id: BlockId| self.first_leaf_in(id);
105                let last = |id: BlockId| self.last_leaf_in(id);
106                let (lo, hi) = if self.cmp_positions(first(*anchor), first(*head))?.is_le() {
107                    (*anchor, *head)
108                } else {
109                    (*head, *anchor)
110                };
111                (first(lo), last(hi))
112            }
113            BlockSelection::Cells { anchor, head } => (
114                BlockPosition::new(*anchor, 0),
115                BlockPosition::new(*head, self.leaf_len(*head).unwrap_or(0)),
116            ),
117        };
118        if self.cmp_positions(a, b)?.is_le() {
119            Ok(ResolvedSelection { start: a, end: b })
120        } else {
121            Ok(ResolvedSelection { start: b, end: a })
122        }
123    }
124
125    pub(crate) fn first_leaf_in(&self, id: BlockId) -> BlockPosition {
126        let mut current = id;
127        while let Some(first) = self.children(current).first() {
128            current = *first;
129        }
130        BlockPosition::new(current, 0)
131    }
132
133    pub(crate) fn last_leaf_in(&self, id: BlockId) -> BlockPosition {
134        let mut current = id;
135        while let Some(last) = self.children(current).last() {
136            current = *last;
137        }
138        BlockPosition::new(current, self.leaf_len(current).unwrap_or(0))
139    }
140}