From ef45b25079d593a8a8409bc252cfaedb5ec189d2 Mon Sep 17 00:00:00 2001 From: Riccardo Mazzarini Date: Sat, 11 Nov 2023 17:19:28 +0100 Subject: [PATCH] implement `MatchedRanges::insert` --- src/algos/fzf/common.rs | 22 ++-- src/algos/fzf/v2.rs | 11 +- src/matched_ranges.rs | 239 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 241 insertions(+), 31 deletions(-) diff --git a/src/algos/fzf/common.rs b/src/algos/fzf/common.rs index cddfed4..17e038b 100644 --- a/src/algos/fzf/common.rs +++ b/src/algos/fzf/common.rs @@ -64,17 +64,9 @@ pub(super) fn calculate_score( }; if with_matched_ranges { - if consecutive == 0 { - let start = range_start + offset; - let end = start + candidate_ch.len_utf8(); - matched_ranges.push(start..end); - } else if let Some(last_range) = matched_ranges.last_mut() { - last_range.end += candidate_ch.len_utf8(); - } else { - unreachable!( - "if consecutive is > 0 we've already pushed a range" - ); - } + let start = range_start + offset; + let end = start + candidate_ch.len_utf8(); + matched_ranges.insert(start..end); } is_in_gap = false; @@ -201,7 +193,7 @@ pub(super) fn exact_match( ); if with_matched_ranges { - matched_ranges.push(matched_range); + matched_ranges.insert(matched_range); } Some(score) @@ -248,7 +240,7 @@ pub(super) fn prefix_match( ); if with_matched_ranges { - matched_ranges.push(matched_range); + matched_ranges.insert(matched_range); } Some(score) @@ -297,7 +289,7 @@ pub(super) fn suffix_match( ); if with_matched_ranges { - matched_ranges.push(matched_range); + matched_ranges.insert(matched_range); } Some(score) @@ -355,7 +347,7 @@ pub(super) fn equal_match( ); if with_matched_ranges { - matched_ranges.push(matched_range); + matched_ranges.insert(matched_range); } Some(score) diff --git a/src/algos/fzf/v2.rs b/src/algos/fzf/v2.rs index 24b72cb..3a43b7e 100644 --- a/src/algos/fzf/v2.rs +++ b/src/algos/fzf/v2.rs @@ -646,16 +646,7 @@ fn matched_ranges( offset += start_offset; - let char_len_utf8 = ch.len_utf8(); - - match ranges.last_mut() { - Some(last) if last.start == offset + char_len_utf8 => { - last.start = offset; - }, - _ => { - ranges.push(offset..offset + char_len_utf8); - }, - } + ranges.insert(offset..offset + ch.len_utf8()); if let Some(up_left) = cell_up_left { cell = up_left; diff --git a/src/matched_ranges.rs b/src/matched_ranges.rs index 159a815..d66137a 100644 --- a/src/matched_ranges.rs +++ b/src/matched_ranges.rs @@ -6,6 +6,12 @@ pub(crate) struct MatchedRanges { ranges: Vec>, } +impl core::fmt::Debug for MatchedRanges { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_list().entries(self.ranges.iter()).finish() + } +} + impl MatchedRanges { /// TODO: docs #[inline(always)] @@ -15,13 +21,234 @@ impl MatchedRanges { /// TODO: docs #[inline(always)] - pub(crate) fn last_mut(&mut self) -> Option<&mut Range> { - self.ranges.last_mut() + pub(crate) fn insert(&mut self, new_range: Range) { + let insert_idx = match self + .ranges + .binary_search_by(|range| range.start.cmp(&new_range.start)) + { + Err(idx) => idx, + + // The range at `idx` and the new range have the same start. + Ok(idx) => { + let (range, next_range) = { + let (left, right) = self.ranges.split_at_mut(idx + 1); + (&mut left[idx], right.get_mut(0)) + }; + + if range.end >= new_range.end { + // The new range is completely contained within this + // existing range. + return; + } + + if let Some(next_range) = next_range { + if new_range.end >= next_range.start { + // The new range fills the gap between this range and + // the next one. + range.end = next_range.end; + self.ranges.remove(idx + 1); + return; + } + } + + range.end = new_range.end; + + return; + }, + }; + + if insert_idx == 0 { + let Some(first_range) = self.ranges.get_mut(0) else { + // This is the first range. + self.ranges.push(new_range); + return; + }; + + if new_range.end >= first_range.start { + first_range.start = new_range.start; + } else { + self.ranges.insert(0, new_range); + } + + return; + } + + if insert_idx == self.ranges.len() { + let last_range = &mut self.ranges[insert_idx - 1]; + + if new_range.start <= last_range.end { + last_range.end = last_range.end.max(new_range.end); + } else { + self.ranges.push(new_range); + } + + return; + } + + let (prev_range, next_range) = { + let (left, right) = self.ranges.split_at_mut(insert_idx); + (&mut left[insert_idx - 1], &mut right[0]) + }; + + match ( + new_range.start <= prev_range.end, + new_range.end >= next_range.start, + ) { + // The new range fills the gap between two existing ranges, so + // we merge them. + // + // ------ ------ => --------------- + // xxxxxxx + (true, true) => { + prev_range.end = next_range.end; + self.ranges.remove(insert_idx); + }, + + // The new range starts within an existing range but ends before + // the next one starts, so we extend the end of the existing range. + // + // ------ ------ => -------- ------ + // xxxx + (true, false) if new_range.end > prev_range.end => { + prev_range.end = new_range.end; + }, + + // The new range ends within an existing range but starts after + // the previous one ends, so we extend the start of the existing + // range. + // + // ------ ------ => ------ -------- + // xxxx + (false, true) => { + next_range.start = new_range.start; + }, + + // The new range is strictly within an existing gap, so we just + // insert it. + // ------ ------ => ------ ----- ------ + // xxxxx + (false, false) => { + self.ranges.insert(insert_idx, new_range); + }, + + _ => {}, + } } +} - /// TODO: docs - #[inline(always)] - pub(crate) fn push(&mut self, range: Range) { - self.ranges.push(range); +#[cfg(test)] +mod tests { + #![allow(clippy::single_range_in_vec_init)] + + use super::*; + + #[test] + fn matched_ranges_insert_same_start_increasing_end() { + let mut ranges = MatchedRanges::default(); + ranges.insert(0..1); + ranges.insert(0..2); + ranges.insert(0..3); + assert_eq!(ranges.as_slice(), [0..3]); + ranges.insert(0..2); + assert_eq!(ranges.as_slice(), [0..3]); + } + + #[test] + fn matched_ranges_insert_consecutive_1() { + let mut ranges = MatchedRanges::default(); + ranges.insert(0..1); + ranges.insert(1..2); + ranges.insert(2..3); + assert_eq!(ranges.as_slice(), [0..3]); + } + + #[test] + fn matched_ranges_insert_consecutive_2() { + let mut ranges = MatchedRanges::default(); + ranges.insert(2..3); + ranges.insert(1..2); + ranges.insert(0..1); + assert_eq!(ranges.as_slice(), [0..3]); + } + + #[test] + fn matched_ranges_insert_fill_gap() { + let mut ranges = MatchedRanges::default(); + ranges.insert(0..1); + ranges.insert(2..3); + assert_eq!(ranges.as_slice(), [0..1, 2..3]); + ranges.insert(1..2); + assert_eq!(ranges.as_slice(), [0..3]); + } + + #[test] + fn matched_ranges_insert_extend_end() { + let mut ranges = MatchedRanges::default(); + ranges.insert(0..2); + ranges.insert(4..6); + ranges.insert(1..3); + assert_eq!(ranges.as_slice(), [0..3, 4..6]); + } + + #[test] + fn matched_ranges_insert_extend_start() { + let mut ranges = MatchedRanges::default(); + ranges.insert(0..2); + ranges.insert(4..6); + ranges.insert(3..5); + assert_eq!(ranges.as_slice(), [0..2, 3..6]); + } + + #[test] + fn matched_ranges_insert_in_gap() { + let mut ranges = MatchedRanges::default(); + ranges.insert(0..4); + ranges.insert(6..8); + ranges.insert(10..14); + assert_eq!(ranges.as_slice(), [0..4, 6..8, 10..14]); + } + + #[test] + fn matched_ranges_insert_smaller_1() { + let mut ranges = MatchedRanges::default(); + ranges.insert(3..8); + ranges.insert(4..7); + assert_eq!(ranges.as_slice(), [3..8]); + ranges.insert(5..6); + assert_eq!(ranges.as_slice(), [3..8]); + } + + #[test] + fn matched_ranges_insert_smaller_2() { + let mut ranges = MatchedRanges::default(); + ranges.insert(1..2); + ranges.insert(3..8); + ranges.insert(4..7); + assert_eq!(ranges.as_slice(), [1..2, 3..8]); + ranges.insert(5..6); + assert_eq!(ranges.as_slice(), [1..2, 3..8]); + } + + #[test] + fn matched_ranges_insert_smaller_3() { + let mut ranges = MatchedRanges::default(); + ranges.insert(10..11); + ranges.insert(3..8); + ranges.insert(4..7); + assert_eq!(ranges.as_slice(), [3..8, 10..11]); + ranges.insert(5..6); + assert_eq!(ranges.as_slice(), [3..8, 10..11]); + } + + #[test] + fn matched_ranges_insert_smaller_4() { + let mut ranges = MatchedRanges::default(); + ranges.insert(1..2); + ranges.insert(10..11); + ranges.insert(3..8); + ranges.insert(4..7); + assert_eq!(ranges.as_slice(), [1..2, 3..8, 10..11]); + ranges.insert(5..6); + assert_eq!(ranges.as_slice(), [1..2, 3..8, 10..11]); } }