yuru_core/lib.rs
1//! Core candidate indexing, fuzzy matching, ranking, and source-span data types
2//! for Yuru.
3//!
4//! This crate is intentionally language-neutral. Japanese, Korean, and Chinese
5//! phonetic keys are supplied by separate backend crates through
6//! [`LanguageBackend`].
7
8/// Candidate indexing and search-key construction.
9pub mod candidate;
10/// Search configuration knobs shared by the CLI and TUI.
11pub mod config;
12/// Internal parser and scorer for fzf-style extended queries.
13mod fzf_query;
14/// Fuzzy and exact matching backends.
15pub mod matcher;
16/// Unicode normalization helpers used before matching.
17pub mod normalize;
18/// Query expansion and key compatibility rules.
19pub mod query;
20/// Candidate ranking and top-result selection.
21pub mod rank;
22/// Counters collected while searching.
23pub mod stats;
24
25use std::fmt;
26use std::str::FromStr;
27
28pub use candidate::{
29 build_candidate, build_index, dedup_and_limit_keys, Candidate, MappedText, MappedTextBuilder,
30 SearchKey, SourceSpan,
31};
32pub use config::{KeyBudget, MatcherAlgo, QueryBudget, SearchConfig, Tiebreak};
33pub use matcher::{
34 match_positions, score_exact_text, score_text, ExactMatcher, GreedyMatcher, MatchPositions,
35 MatcherBackend, NucleoMatcher,
36};
37pub use query::{
38 base_query_variants, dedup_and_limit_variants, key_kind_allowed, PlainBackend, QueryVariant,
39};
40pub use rank::{search, search_with_stats, ScoredCandidate};
41pub use stats::SearchStats;
42
43#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
44/// Language backend selected for one search run.
45pub enum LangMode {
46 /// No language-specific phonetic expansion.
47 Plain,
48 /// Japanese kana and romaji expansion.
49 Japanese,
50 /// Korean Hangul romanization, initials, and keyboard expansion.
51 Korean,
52 /// Chinese pinyin and initials expansion.
53 Chinese,
54 /// Japanese, Korean, and Chinese expansion together.
55 All,
56}
57
58impl fmt::Display for LangMode {
59 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60 match self {
61 LangMode::Plain => f.write_str("plain"),
62 LangMode::Japanese => f.write_str("ja"),
63 LangMode::Korean => f.write_str("ko"),
64 LangMode::Chinese => f.write_str("zh"),
65 LangMode::All => f.write_str("all"),
66 }
67 }
68}
69
70impl FromStr for LangMode {
71 type Err = String;
72
73 fn from_str(value: &str) -> Result<Self, Self::Err> {
74 match value {
75 "plain" => Ok(LangMode::Plain),
76 "ja" | "japanese" => Ok(LangMode::Japanese),
77 "ko" | "korean" => Ok(LangMode::Korean),
78 "zh" | "chinese" => Ok(LangMode::Chinese),
79 "all" => Ok(LangMode::All),
80 other => Err(format!("unsupported language mode: {other}")),
81 }
82 }
83}
84
85#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
86/// Kind of indexed key attached to a candidate.
87pub enum KeyKind {
88 /// Original display text.
89 Original,
90 /// Normalized display text.
91 Normalized,
92 /// Japanese kana reading.
93 KanaReading,
94 /// Japanese romaji reading.
95 RomajiReading,
96 /// Chinese pinyin syllables separated by spaces.
97 PinyinFull,
98 /// Chinese pinyin joined without separators.
99 PinyinJoined,
100 /// Chinese pinyin initials.
101 PinyinInitials,
102 /// Korean romanized Hangul.
103 KoreanRomanized,
104 /// Korean Hangul initial consonants.
105 KoreanInitials,
106 /// Korean keyboard-layout spelling.
107 KoreanKeyboard,
108 /// User-learned alias key.
109 LearnedAlias,
110}
111
112#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
113/// Kind of query expansion produced before scoring.
114pub enum QueryVariantKind {
115 /// Query text exactly as entered.
116 Original,
117 /// Normalized query text.
118 Normalized,
119 /// Kana query text.
120 Kana,
121 /// Romaji query converted to kana.
122 RomajiToKana,
123 /// Pinyin query text.
124 Pinyin,
125 /// Initial-letter query text.
126 Initials,
127}
128
129/// Language-specific candidate and query expansion.
130pub trait LanguageBackend: Send + Sync {
131 /// Returns the language mode implemented by this backend.
132 fn mode(&self) -> LangMode;
133
134 /// Normalizes candidate display text before the base normalized key is added.
135 ///
136 /// Extended-query exact matching reuses this key instead of re-folding the
137 /// original text per candidate, and skips it entirely when it is only a case-folded
138 /// copy of the display text, so an override must stay equivalent to
139 /// [`normalize::normalize`] for case- and width-insensitive comparison.
140 fn normalize_candidate(&self, text: &str) -> String {
141 normalize::normalize(text)
142 }
143
144 /// Builds additional language-specific search keys for candidate text.
145 fn build_candidate_keys(&self, text: &str, budget: KeyBudget) -> Vec<SearchKey>;
146
147 /// Expands a user query into language-specific query variants.
148 fn expand_query(&self, query: &str, budget: QueryBudget) -> Vec<QueryVariant>;
149}