layered_nlp/ll_line/
ll_selection.rs

1use super::x::{XBackwards, XForwards};
2use super::{assert_ll_lines_equals, LLCursorAssignment, LLLine, Rc, XMatch};
3
4// # List of operations
5//
6// Cutting Selection (selection) -> Iter<selection>
7//  - split_by_x             :: [aaaxaaaxaaa] -> [aaa]x[aaa]x[aaa]
8//  - find_by_x              :: [aaaxaaaxaaa] -> aaa[x]aaa[x]aaa
9// skips overlapping
10//  - split_by_forwards_x    :: [aaaxaaaxaaa] -> [aaa]x[aaa]x[aaa]
11//  - find_by_forwards_x     :: [aaaxaaaxaaa] -> aaa[x]aaa[x]aaa
12// skips overlapping
13//  - split_by_backwards_x   :: [aaaxaaaxaaa] -> [aaa]x[aaa]x[aaa]
14//  - find_by_backwards_x    :: [aaaxaaaxaaa] -> aaa[x]aaa[x]aaa
15// Expand Selection (selection) -> Iter<selection>
16//  - match_forwards_x          :: x[aaa]x -> x[aaax]
17//  - match_backwards_x         :: x[aaa]x -> [xaaa]x
18// Shrinking Selection (selection) -> Option<selection>
19//  - trim_x                    :: [xaaax] -> x[aaa]x
20//  - trim_leading_x            :: [xaaax] -> x[aaax]
21//  - trim_trailing_x           :: [xaaax] -> [xaaa]x
22
23// x
24// start large, then test
25
26/// Selections will never be empty
27#[derive(Clone)]
28pub struct LLSelection {
29    pub(super) ll_line: Rc<LLLine>,
30    /// Where to begin in the line (inclusive, default is 0)
31    pub(super) start_idx: usize,
32    /// Where to end in the line (inclusive, default is last idx)
33    pub(super) end_idx: usize,
34}
35
36impl std::fmt::Debug for LLSelection {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        f.debug_struct("LLSelection")
39            .field("start_idx", &self.start_idx)
40            .field("end_idx", &self.end_idx)
41            .finish()
42    }
43}
44
45impl PartialEq for LLSelection {
46    fn eq(&self, other: &Self) -> bool {
47        self.start_idx == other.start_idx
48            && self.end_idx == other.end_idx
49            && Rc::ptr_eq(&self.ll_line, &other.ll_line)
50    }
51}
52
53impl LLSelection {
54    /// Returns None if the line is empty
55    pub fn from_line(ll_line: Rc<LLLine>) -> Option<Self> {
56        let line_len = ll_line.ll_tokens.len();
57        if line_len > 0 {
58            Some(LLSelection {
59                ll_line,
60                end_idx: line_len - 1,
61                start_idx: 0,
62            })
63        } else {
64            None // empty selection
65        }
66    }
67
68    pub fn split_by<'a, M: XMatch<'a>>(&'a self, matcher: &M) -> Vec<LLSelection> {
69        let matches = self.find_by(matcher);
70
71        if matches.is_empty() {
72            return vec![self.clone()];
73        }
74
75        if matches.len() == 1 {
76            return matches[0]
77                .0
78                .start_idx
79                .checked_sub(1)
80                .and_then(|end_idx| self.selection_from(self.start_idx, end_idx))
81                .into_iter()
82                .chain(self.selection_from(matches[0].0.end_idx + 1, self.end_idx))
83                .collect();
84        }
85
86        let start_opt = matches[0]
87            .0
88            .start_idx
89            .checked_sub(1)
90            .and_then(|end_idx| self.selection_from(self.start_idx, end_idx));
91
92        let end_opt = self.selection_from(matches.last().unwrap().0.end_idx + 1, self.end_idx);
93        start_opt
94            .into_iter()
95            .chain(matches.windows(2).into_iter().filter_map(|m| {
96                m[1].0
97                    .start_idx
98                    .checked_sub(1)
99                    .and_then(|end_idx| self.selection_from(m[0].0.end_idx + 1, end_idx))
100            }))
101            .chain(end_opt)
102            .collect()
103    }
104
105    pub fn find_by<'a, M: XMatch<'a>>(&'a self, matcher: &M) -> Vec<(LLSelection, M::Out)> {
106        (self.start_idx..=self.end_idx)
107            .map(|i| {
108                let forwards = XForwards { from_idx: i };
109
110                matcher
111                    .go(&forwards, &self.ll_line)
112                    .into_iter()
113                    .map(move |(out, next_idx)| {
114                        (
115                            LLSelection {
116                                start_idx: i,
117                                end_idx: next_idx.0,
118                                ll_line: self.ll_line.clone(),
119                            },
120                            out,
121                        )
122                    })
123            })
124            .flatten()
125            .collect()
126    }
127
128    pub fn find_first_by<'a, M: XMatch<'a>>(
129        &'a self,
130        matcher: &M,
131    ) -> Option<(LLSelection, M::Out)> {
132        self.find_by(matcher).into_iter().next()
133    }
134
135    pub fn find_by_forwards_and_backwards<'a, M: XMatch<'a>>(
136        &'a self,
137        matcher: &M,
138    ) -> Vec<(LLSelection, M::Out)> {
139        (self.start_idx..=self.end_idx)
140            .map(|i| {
141                let forwards = XForwards { from_idx: i };
142
143                matcher
144                    .go(&forwards, &self.ll_line)
145                    .into_iter()
146                    .map(move |(out, next_idx)| {
147                        (
148                            LLSelection {
149                                start_idx: i,
150                                end_idx: next_idx.0,
151                                ll_line: self.ll_line.clone(),
152                            },
153                            out,
154                        )
155                    })
156                    .chain({
157                        let backwards = XBackwards { from_idx: i };
158
159                        matcher.go(&backwards, &self.ll_line).into_iter().map(
160                            move |(out, next_idx)| {
161                                (
162                                    LLSelection {
163                                        start_idx: next_idx.0,
164                                        end_idx: i,
165                                        ll_line: self.ll_line.clone(),
166                                    },
167                                    out,
168                                )
169                            },
170                        )
171                    })
172            })
173            .flatten()
174            .collect()
175    }
176
177    pub fn match_forwards<'a, M: XMatch<'a>>(&'a self, matcher: &M) -> Vec<(LLSelection, M::Out)> {
178        // [ ... ] - Current selection
179        //        [ ... ] - Trying to match Attr
180        if self.end_idx + 1 == self.ll_line.ll_tokens.len() {
181            return Vec::new();
182        }
183
184        let forwards = XForwards {
185            from_idx: self.end_idx + 1,
186        };
187
188        matcher
189            .go(&forwards, &self.ll_line)
190            .into_iter()
191            .map(|(out, next_idx)| {
192                (
193                    LLSelection {
194                        start_idx: self.start_idx,
195                        end_idx: next_idx.0,
196                        ll_line: self.ll_line.clone(),
197                    },
198                    out,
199                )
200            })
201            .collect()
202    }
203
204    pub fn match_first_forwards<'a, M: XMatch<'a>>(
205        &'a self,
206        matcher: &M,
207    ) -> Option<(LLSelection, M::Out)> {
208        self.match_forwards(matcher).into_iter().next()
209    }
210
211    pub fn match_forwards_longest<'a, M: XMatch<'a>>(
212        &'a self,
213        _matcher: &M,
214    ) -> Option<(LLSelection, M::Out)> {
215        todo!()
216    }
217
218    pub fn match_forwards_shortest<'a, M: XMatch<'a>>(
219        &'a self,
220        _matcher: &M,
221    ) -> Option<(LLSelection, M::Out)> {
222        todo!()
223    }
224
225    pub fn match_backwards<'a, M: XMatch<'a>>(&'a self, matcher: &M) -> Vec<(LLSelection, M::Out)> {
226        if self.start_idx == 0 {
227            return Vec::new();
228        }
229
230        let backwards = XBackwards {
231            from_idx: self.start_idx - 1,
232        };
233
234        matcher
235            .go(&backwards, &self.ll_line)
236            .into_iter()
237            .map(|(out, next_idx)| {
238                (
239                    LLSelection {
240                        start_idx: next_idx.0,
241                        end_idx: self.end_idx,
242                        ll_line: self.ll_line.clone(),
243                    },
244                    out,
245                )
246            })
247            .collect()
248    }
249
250    pub fn match_first_backwards<'a, M: XMatch<'a>>(
251        &'a self,
252        matcher: &M,
253    ) -> Option<(LLSelection, M::Out)> {
254        self.match_backwards(matcher).into_iter().next()
255    }
256
257    pub fn after(&self) -> Option<LLSelection> {
258        let ll_line_end = self.ll_line.ll_tokens.len() - 1;
259
260        if self.start_idx + 1 == ll_line_end {
261            None
262        } else {
263            Some(LLSelection {
264                ll_line: self.ll_line.clone(),
265                start_idx: self.end_idx + 1,
266                end_idx: ll_line_end,
267            })
268        }
269    }
270
271    fn selection_from(&self, mut start_idx: usize, mut end_idx: usize) -> Option<LLSelection> {
272        start_idx = start_idx.max(self.start_idx);
273        end_idx = end_idx.min(self.end_idx);
274        if start_idx <= end_idx {
275            Some(LLSelection {
276                ll_line: self.ll_line.clone(),
277                end_idx,
278                start_idx,
279            })
280        } else {
281            None
282        }
283    }
284
285    // Hmmm... TODO: Unit test this thoroughly
286    pub fn split_with(&self, other_selection: &LLSelection) -> [Option<LLSelection>; 2] {
287        assert_ll_lines_equals(&self.ll_line, &other_selection.ll_line);
288
289        // 1   [          ]
290        // 2                   [      ]
291        //     [          ]
292        //               ]      [
293        // 1   [          ]
294        // 2           [      ]
295        //     [      ]
296
297        [
298            if other_selection.start_idx > 0 {
299                // underflow protection
300                self.selection_from(self.start_idx, other_selection.start_idx - 1)
301            } else {
302                None
303            },
304            self.selection_from(other_selection.end_idx + 1, self.end_idx),
305        ]
306    }
307
308    /// Private impl which makes it easier to reuse this logic without facing borrowing issues
309    fn trim_selection<'a, M: XMatch<'a>>(
310        &'a self,
311        matcher: &M,
312        from_start: bool,
313        from_end: bool,
314    ) -> Option<LLSelection> {
315        let mut new_start = self.start_idx;
316        let mut new_end = self.end_idx;
317
318        if from_start {
319            if let Some(first_match) = matcher
320                .go(
321                    &XForwards {
322                        from_idx: self.start_idx,
323                    },
324                    &self.ll_line,
325                )
326                .first()
327            {
328                new_start = (first_match.1).0 + 1;
329            }
330        }
331
332        if from_end {
333            if let Some(first_match) = matcher
334                .go(
335                    &XBackwards {
336                        from_idx: self.end_idx,
337                    },
338                    &self.ll_line,
339                )
340                .first()
341            {
342                new_end = (first_match.1)
343                    .0
344                    .checked_sub(1)
345                    .unwrap_or((first_match.1).0);
346            }
347        }
348
349        if new_end >= self.start_idx && new_start <= new_end {
350            Some(LLSelection {
351                ll_line: self.ll_line.clone(),
352                start_idx: new_start,
353                end_idx: new_end,
354            })
355        } else {
356            None
357        }
358    }
359
360    pub fn trim_start<'a, M: XMatch<'a>>(&'a self, matcher: &M) -> Option<LLSelection> {
361        self.trim_selection(matcher, true, false)
362    }
363
364    pub fn trim_end<'a, M: XMatch<'a>>(&'a self, matcher: &M) -> Option<LLSelection> {
365        self.trim_selection(matcher, false, true)
366    }
367
368    pub fn trim<'a, M: XMatch<'a>>(&'a self, matcher: &M) -> Option<LLSelection> {
369        self.trim_selection(matcher, true, true)
370    }
371
372    pub fn finish_with_attr<Attr>(&self, value: Attr) -> LLCursorAssignment<Attr> {
373        LLCursorAssignment {
374            end_idx: self.end_idx,
375            start_idx: self.start_idx,
376            value,
377        }
378    }
379}