norm/metrics/fzf/
scheme.rs

1use super::{bonus, CharClass, Score};
2
3/// A type used to tweak the distance algorithm.
4///
5/// This enum can be passed to both [`FzfV1`](super::FzfV1) and
6/// [`FzfV2`](super::FzfV2) to tweak the distance algorithm based on the type
7/// of candidates being searched.
8#[derive(Debug, Default, Clone, Copy, Ord, PartialOrd, Eq, PartialEq)]
9pub enum FzfScheme {
10    /// A generic distance scheme that works well for any type of input.
11    #[default]
12    Default,
13
14    /// A distance scheme tailored for searching file paths. It assigns
15    /// additional bonus points to the character immediately following a path
16    /// separator (i.e. `/` on Unix-like systems and `\` on Windows).
17    Path,
18
19    /// A distance scheme tailored for searching shell command history which
20    /// doesn't assign any additional bonus points.
21    History,
22}
23
24impl FzfScheme {
25    /// TODO: docs
26    #[inline]
27    pub(super) fn into_inner(self) -> Scheme {
28        match self {
29            Self::Default => DEFAULT,
30            Self::Path => PATH,
31            Self::History => HISTORY,
32        }
33    }
34
35    /// TODO: docs
36    #[inline]
37    pub(super) fn from_inner(scheme: &Scheme) -> Option<Self> {
38        if scheme.bonus_boundary_white == DEFAULT.bonus_boundary_white {
39            Some(Self::Default)
40        } else if scheme.bonus_boundary_white == PATH.bonus_boundary_white {
41            if scheme.initial_char_class == CharClass::Delimiter {
42                Some(Self::Path)
43            } else {
44                Some(Self::History)
45            }
46        } else {
47            None
48        }
49    }
50}
51
52/// TODO: docs
53#[doc(hidden)]
54#[derive(Clone)]
55pub struct Scheme {
56    pub bonus_boundary_white: Score,
57    pub bonus_boundary_delimiter: Score,
58    pub(super) initial_char_class: CharClass,
59    pub(super) is_delimiter: fn(char) -> bool,
60}
61
62impl Default for Scheme {
63    #[inline]
64    fn default() -> Self {
65        DEFAULT
66    }
67}
68
69/// TODO: docs
70pub const DEFAULT: Scheme = Scheme {
71    bonus_boundary_white: bonus::BOUNDARY + 2,
72    bonus_boundary_delimiter: bonus::BOUNDARY + 1,
73    initial_char_class: CharClass::WhiteSpace,
74    is_delimiter: is_delimiter_default,
75};
76
77#[inline]
78fn is_delimiter_default(ch: char) -> bool {
79    matches!(ch, '/' | ',' | ':' | ';' | '|')
80}
81
82/// TODO: docs
83pub const PATH: Scheme = Scheme {
84    bonus_boundary_white: bonus::BOUNDARY,
85    bonus_boundary_delimiter: bonus::BOUNDARY + 1,
86    initial_char_class: CharClass::Delimiter,
87    is_delimiter: is_delimiter_path,
88};
89
90#[inline]
91fn is_delimiter_path(ch: char) -> bool {
92    // Using `std::path::MAIN_SEPARATOR` would force us to depend on `std`
93    // instead of `core + alloc`, so we use a custom implementation.
94    #[cfg(windows)]
95    let os_path_separator = '\\';
96    #[cfg(not(windows))]
97    let os_path_separator = '/';
98
99    ch == '/' || ch == os_path_separator
100}
101
102/// TODO: docs
103pub const HISTORY: Scheme = Scheme {
104    bonus_boundary_white: bonus::BOUNDARY,
105    bonus_boundary_delimiter: bonus::BOUNDARY,
106    initial_char_class: DEFAULT.initial_char_class,
107    is_delimiter: DEFAULT.is_delimiter,
108};