1use std::collections::BTreeMap;
21
22use blockworx_doc::rev::Rev;
23
24use super::record::{Anchor, LogRecord, NoteDisplay, RecordKind};
25
26#[derive(Clone, Debug, PartialEq, Eq)]
28pub struct Note {
29 pub text: String,
30 pub anchor: Option<Anchor>,
31 pub show: NoteDisplay,
32}
33
34#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38enum Standing {
39 Shown,
40 Hidden,
41}
42
43#[derive(Clone, Debug, PartialEq, Eq)]
45struct Held {
46 note: Note,
47 standing: Standing,
48}
49
50#[derive(Clone, Debug, Default, PartialEq, Eq)]
52pub struct Notes {
53 notes: BTreeMap<Rev, Held>,
54 stands_for: BTreeMap<Rev, Rev>,
59}
60
61impl Notes {
62 pub fn take(&mut self, record: &LogRecord) {
66 match record.kind {
67 RecordKind::Note { anchor, show } => {
68 self.written(record.rev, &record.label, anchor, show);
69 }
70 RecordKind::Undo { of } => self.undone(record.rev, of),
71 RecordKind::Redo { of } => self.redone(record.rev, of),
72 RecordKind::Edit | RecordKind::Tag => {}
73 }
74 }
75
76 pub fn written(&mut self, at: Rev, text: &str, anchor: Option<Anchor>, show: NoteDisplay) {
81 self.notes.insert(
82 at,
83 Held {
84 note: Note {
85 text: text.to_owned(),
86 anchor,
87 show,
88 },
89 standing: Standing::Shown,
90 },
91 );
92 self.stands_for.insert(at, at);
93 }
94
95 pub fn undone(&mut self, at: Rev, of: Rev) {
97 self.moved(at, of, Standing::Hidden);
98 }
99
100 pub fn redone(&mut self, at: Rev, of: Rev) {
102 self.moved(at, of, Standing::Shown);
103 }
104
105 fn moved(&mut self, at: Rev, of: Rev, standing: Standing) {
106 let Some(note) = self.stands_for.get(&of).copied() else {
107 return;
108 };
109 if let Some(held) = self.notes.get_mut(¬e) {
110 held.standing = standing;
111 }
112 self.stands_for.insert(at, note);
113 }
114
115 pub fn get(&self, at: Rev) -> Option<&Note> {
118 self.notes.get(&at).map(|held| &held.note)
119 }
120
121 pub fn is_shown(&self, at: Rev) -> bool {
123 self.notes
124 .get(&at)
125 .is_some_and(|held| held.standing == Standing::Shown)
126 }
127
128 pub fn shown_at(&self, at: Rev) -> Option<&Note> {
131 let held = self.notes.get(&at)?;
132 (held.standing == Standing::Shown).then_some(&held.note)
133 }
134
135 pub fn iter(&self) -> impl Iterator<Item = (Rev, &Note)> {
137 self.notes.iter().map(|(rev, held)| (*rev, &held.note))
138 }
139
140 pub fn shown(&self) -> impl Iterator<Item = (Rev, &Note)> {
142 self.notes
143 .iter()
144 .filter(|(_, held)| held.standing == Standing::Shown)
145 .map(|(rev, held)| (*rev, &held.note))
146 }
147
148 pub fn len(&self) -> usize {
149 self.notes.len()
150 }
151
152 pub fn is_empty(&self) -> bool {
153 self.notes.is_empty()
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160 use blockworx_doc::fixtures::rev;
161
162 fn narration(text: &str) -> Note {
163 Note {
164 text: text.to_owned(),
165 anchor: None,
166 show: NoteDisplay::Narration,
167 }
168 }
169
170 fn notes() -> Notes {
171 let mut notes = Notes::default();
172 notes.written(rev(1), "Earlier", None, NoteDisplay::Narration);
173 notes.written(rev(2), "Later", None, NoteDisplay::Narration);
174 notes
175 }
176
177 #[test]
178 fn the_last_note_for_a_rev_wins() {
179 let mut notes = Notes::default();
180 notes.written(rev(2), "First", None, NoteDisplay::Narration);
181 notes.written(rev(2), "Second", None, NoteDisplay::Chapter);
182 assert_eq!(notes.len(), 1);
183 assert_eq!(
184 notes.get(rev(2)).map(|note| note.show),
185 Some(NoteDisplay::Chapter),
186 );
187 }
188
189 #[test]
190 fn notes_read_back_oldest_first() {
191 assert_eq!(
192 notes()
193 .iter()
194 .map(|(rev, note)| (rev, note.text.clone()))
195 .collect::<Vec<_>>(),
196 [(rev(1), "Earlier".to_owned()), (rev(2), "Later".to_owned()),],
197 );
198 }
199
200 #[test]
205 fn an_undo_hides_the_note_it_names_and_a_redo_of_the_undo_brings_it_back() {
206 let mut notes = notes();
207 assert_eq!(notes.shown().count(), 2, "precondition: both stand");
208
209 notes.undone(rev(3), rev(2));
210 assert_eq!(notes.shown_at(rev(2)), None);
211 assert_eq!(notes.shown_at(rev(1)), Some(&narration("Earlier")));
212 assert_eq!(
213 notes.get(rev(2)),
214 Some(&narration("Later")),
215 "the record is still in the log — it is the showing that stopped",
216 );
217
218 notes.redone(rev(4), rev(3));
219 assert_eq!(notes.shown_at(rev(2)), Some(&narration("Later")));
220
221 notes.undone(rev(5), rev(4));
222 assert_eq!(notes.shown_at(rev(2)), None, "and back again, any depth");
223 }
224
225 #[test]
229 fn a_note_the_history_forked_away_from_stays_hidden() {
230 let mut notes = notes();
231 notes.undone(rev(3), rev(2));
232 notes.undone(rev(5), rev(4));
235 assert_eq!(notes.shown_at(rev(2)), None);
236 assert_eq!(notes.shown().count(), 1);
237 }
238
239 #[test]
242 fn an_undo_of_an_ordinary_edit_touches_no_note() {
243 let mut notes = notes();
244 notes.undone(rev(9), rev(8));
245 assert_eq!(notes.shown().count(), 2);
246 assert!(!notes.stands_for.contains_key(&rev(9)));
247 }
248}