1pub(crate) mod component;
2mod matching;
3mod render;
4
5use std::sync::Arc;
6
7use nucleo::pattern::{CaseMatching, Normalization};
8
9use crate::callback::{Callback, KeyHandler};
10use crate::core::element::{Element, Key};
11use crate::style::{
12 BorderStyle, CaretShape, Color, Length, Padding, ScrollbarConfig, Style, StyleSlot,
13};
14use crate::utils::gradient::{ColorGradient, GradientRange};
15use crate::widgets::{ListConfig, ListItem, ListItemGutter, ListItemStatus};
16
17pub(crate) const DEFAULT_SYNC_MATCH_LIMIT: usize = 100;
18
19#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
41pub struct ItemDescription {
42 pub left: Option<Arc<str>>,
44 pub right: Option<Arc<str>>,
46}
47
48impl ItemDescription {
49 pub fn new() -> Self {
51 Self::default()
52 }
53
54 pub fn left(mut self, text: impl Into<Arc<str>>) -> Self {
56 self.left = Some(text.into());
57 self
58 }
59
60 pub fn right(mut self, text: impl Into<Arc<str>>) -> Self {
62 self.right = Some(text.into());
63 self
64 }
65}
66
67impl From<&str> for ItemDescription {
68 fn from(s: &str) -> Self {
69 Self {
70 left: Some(s.into()),
71 right: None,
72 }
73 }
74}
75
76impl From<String> for ItemDescription {
77 fn from(s: String) -> Self {
78 Self {
79 left: Some(s.into()),
80 right: None,
81 }
82 }
83}
84
85impl From<Arc<str>> for ItemDescription {
86 fn from(s: Arc<str>) -> Self {
87 Self {
88 left: Some(s),
89 right: None,
90 }
91 }
92}
93
94#[derive(Clone, Debug, PartialEq)]
96pub struct SearchItem<T> {
97 pub label: Arc<str>,
99 pub description: Option<ItemDescription>,
101 pub aliases: Vec<Arc<str>>,
106 pub active: bool,
108 pub priority: i32,
112 pub value: T,
114}
115
116impl<T> SearchItem<T> {
117 pub fn new(label: impl Into<Arc<str>>, value: T) -> Self {
119 Self {
120 label: label.into(),
121 description: None,
122 aliases: Vec::new(),
123 active: false,
124 priority: 0,
125 value,
126 }
127 }
128
129 pub fn description(mut self, description: impl Into<ItemDescription>) -> Self {
131 self.description = Some(description.into());
132 self
133 }
134
135 pub fn aliases<I, S>(mut self, aliases: I) -> Self
137 where
138 I: IntoIterator<Item = S>,
139 S: Into<Arc<str>>,
140 {
141 self.aliases = aliases.into_iter().map(Into::into).collect();
142 self
143 }
144
145 pub fn alias(mut self, alias: impl Into<Arc<str>>) -> Self {
147 self.aliases.push(alias.into());
148 self
149 }
150
151 pub fn active(mut self, active: bool) -> Self {
153 self.active = active;
154 self
155 }
156
157 pub fn priority(mut self, priority: i32) -> Self {
163 self.priority = priority;
164 self
165 }
166}
167
168pub fn rank_search_palette_indices<T: Clone + PartialEq>(
173 items: &[SearchItem<T>],
174 query: &str,
175) -> Vec<usize> {
176 rank_search_palette_indices_with_score(items, query, |_, _, score| score as f64)
177}
178
179pub fn rank_search_palette_indices_with_score<T: Clone + PartialEq, F>(
191 items: &[SearchItem<T>],
192 query: &str,
193 score_fn: F,
194) -> Vec<usize>
195where
196 F: FnMut(usize, &SearchItem<T>, u32) -> f64,
197{
198 rank_search_palette_indices_with_mode(items, query, SearchMatchMode::Fuzzy, score_fn)
199}
200
201pub fn rank_search_palette_indices_with_mode<T: Clone + PartialEq, F>(
213 items: &[SearchItem<T>],
214 query: &str,
215 match_mode: SearchMatchMode,
216 mut score_fn: F,
217) -> Vec<usize>
218where
219 F: FnMut(usize, &SearchItem<T>, u32) -> f64,
220{
221 let query = query.trim();
222 if query.is_empty() {
223 return (0..items.len()).collect();
224 }
225 let entries = matching::build_search_entries(items);
226 let mut results: Vec<_> = matching::match_items(
227 &entries,
228 query,
229 match_mode,
230 CaseMatching::Smart,
231 Normalization::Smart,
232 )
233 .into_iter()
234 .map(|result| {
235 let item_index = result.item_index;
236 let adjusted_score = score_fn(item_index, &items[item_index], result.score);
237 (item_index, adjusted_score)
238 })
239 .collect();
240
241 results.sort_by(|(a_index, a_score), (b_index, b_score)| {
242 match (a_score.is_nan(), b_score.is_nan()) {
243 (true, true) => a_index.cmp(b_index),
244 (true, false) => std::cmp::Ordering::Greater,
245 (false, true) => std::cmp::Ordering::Less,
246 (false, false) => b_score
247 .partial_cmp(a_score)
248 .unwrap_or(std::cmp::Ordering::Equal)
249 .then(a_index.cmp(b_index)),
250 }
251 });
252
253 results
254 .into_iter()
255 .map(|(item_index, _)| item_index)
256 .collect()
257}
258
259#[cfg(test)]
260mod tests {
261 use super::{
262 SearchItem, SearchMatchMode, rank_search_palette_indices,
263 rank_search_palette_indices_with_mode, rank_search_palette_indices_with_score,
264 };
265
266 #[test]
267 fn rank_search_palette_indices_uses_identity_scoring() {
268 let items = vec![
269 SearchItem::new("alpha", 0),
270 SearchItem::new("alpine", 1),
271 SearchItem::new("beta", 2),
272 ];
273
274 let ranked = rank_search_palette_indices(&items, "alp");
275 let ranked_with_identity =
276 rank_search_palette_indices_with_score(&items, "alp", |_, _, score| score as f64);
277
278 assert_eq!(ranked_with_identity, ranked);
279 }
280
281 #[test]
282 fn items_arc_and_entries_arc_preserve_shared_slices() {
283 use super::{SearchEntry, SearchPalette};
284 use std::sync::Arc;
285
286 let items: Arc<[SearchItem<usize>]> =
287 Arc::from([SearchItem::new("alpha", 0), SearchItem::new("beta", 1)]);
288 let palette = SearchPalette::new().items_arc(Arc::clone(&items));
289 assert!(Arc::ptr_eq(&palette.props.items, &items));
290 assert!(palette.props.entries.is_empty());
291
292 let entries: Arc<[SearchEntry<usize>]> =
293 Arc::from([SearchEntry::header("Group"), SearchEntry::item("gamma", 2)]);
294 let palette = SearchPalette::new().entries_arc(Arc::clone(&entries));
295 assert!(Arc::ptr_eq(&palette.props.entries, &entries));
296 assert_eq!(palette.props.items.len(), 1);
297 assert_eq!(palette.props.items[0].label.as_ref(), "gamma");
298 }
299
300 #[test]
301 fn rank_search_palette_indices_with_score_can_reorder_matches() {
302 let items = vec![
303 SearchItem::new("alpha", 0),
304 SearchItem::new("alpine", 1),
305 SearchItem::new("beta", 2),
306 ];
307
308 let ranked = rank_search_palette_indices_with_score(&items, "alp", |index, _, score| {
309 if index == 1 {
310 score as f64 + 1_000_000.0
311 } else {
312 score as f64
313 }
314 });
315
316 assert_eq!(ranked, vec![1, 0]);
317 }
318
319 #[test]
320 fn rank_search_palette_indices_with_score_does_not_score_empty_query() {
321 let items = vec![SearchItem::new("alpha", 0), SearchItem::new("alpine", 1)];
322
323 let ranked = rank_search_palette_indices_with_score(&items, " ", |_, _, _| {
324 panic!("empty queries must not call custom scoring")
325 });
326
327 assert_eq!(ranked, vec![0, 1]);
328 }
329
330 #[test]
331 fn rank_search_palette_indices_with_score_places_nan_after_finite_scores() {
332 let items = vec![
333 SearchItem::new("alpha", 0),
334 SearchItem::new("alpine", 1),
335 SearchItem::new("atlas", 2),
336 ];
337
338 let ranked = rank_search_palette_indices_with_score(&items, "a", |index, _, _| {
339 if index == 1 { 1.0 } else { f64::NAN }
340 });
341
342 assert_eq!(ranked, vec![1, 0, 2]);
343 }
344
345 #[test]
346 fn rank_search_palette_indices_with_mode_applies_hybrid_gating() {
347 let items = vec![
348 SearchItem::new("Enable pane synchronization", 0),
349 SearchItem::new("Layout", 1),
350 ];
351
352 let hybrid = rank_search_palette_indices_with_mode(
354 &items,
355 "layo",
356 SearchMatchMode::Hybrid,
357 |_, _, score| score as f64,
358 );
359 assert_eq!(hybrid, vec![1]);
360
361 let fuzzy =
363 rank_search_palette_indices_with_score(&items, "layo", |_, _, score| score as f64);
364 assert!(fuzzy.contains(&0));
365 }
366}
367
368#[derive(Clone, Debug, PartialEq)]
370pub struct SearchEvent<T> {
371 pub match_index: usize,
373 pub item_index: usize,
375 pub item: SearchItem<T>,
377}
378
379#[derive(Clone, Debug, Default, PartialEq, Eq)]
381pub struct SearchHighlight {
382 pub label_hits: Vec<u32>,
384 pub description_hits: Vec<u32>,
386 pub description_right_hits: Vec<u32>,
388 pub score: u32,
390}
391
392type SearchRenderer<T> = Arc<dyn Fn(&SearchItem<T>, &SearchHighlight) -> Option<ListItem>>;
394
395type SearchGutterRenderer<T> =
397 Arc<dyn Fn(&SearchItem<T>, &SearchHighlight) -> Option<ListItemGutter>>;
398
399type SearchStatusRenderer<T> =
401 Arc<dyn Fn(&SearchItem<T>, &SearchHighlight) -> Option<ListItemStatus>>;
402
403#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
405pub enum DescriptionPlacement {
406 #[default]
408 Inline,
409 Right,
411 Above,
413 Below,
415}
416
417#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
419pub enum DescriptionOverflow {
420 #[default]
422 Truncate,
423 Wrap,
426}
427
428#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
430pub enum SearchMatchMode {
431 #[default]
434 Fuzzy,
435 Hybrid,
451}
452
453#[derive(Clone, Debug, PartialEq)]
460pub enum SearchEntry<T> {
461 Item(SearchItem<T>),
463 Header(Arc<str>),
465 Spacer,
467}
468
469impl<T> SearchEntry<T> {
470 pub fn item(label: impl Into<Arc<str>>, value: T) -> Self {
472 Self::Item(SearchItem::new(label, value))
473 }
474
475 pub fn header(label: impl Into<Arc<str>>) -> Self {
477 Self::Header(label.into())
478 }
479
480 pub fn spacer() -> Self {
482 Self::Spacer
483 }
484
485 pub fn description(self, description: impl Into<ItemDescription>) -> Self {
488 match self {
489 Self::Item(item) => Self::Item(item.description(description)),
490 other => other,
491 }
492 }
493
494 pub fn active(self, active: bool) -> Self {
496 match self {
497 Self::Item(item) => Self::Item(item.active(active)),
498 other => other,
499 }
500 }
501
502 pub fn priority(self, priority: i32) -> Self {
504 match self {
505 Self::Item(item) => Self::Item(item.priority(priority)),
506 other => other,
507 }
508 }
509}
510
511#[derive(Clone)]
512#[allow(missing_docs)]
513pub(crate) struct SearchPaletteProps<T> {
514 items: Arc<[SearchItem<T>]>,
515 entries: Arc<[SearchEntry<T>]>,
516 sync_match_limit: usize,
517 sync_selection: bool,
518 initial_query: Arc<str>,
519 initial_selected_item_index: Option<usize>,
523 query: Option<Arc<str>>,
526 placeholder: Arc<str>,
527 width: Length,
529 height: Length,
530 max_width: Option<Length>,
531 max_height: Option<Length>,
532 input_prefix: Option<Arc<str>>,
534 input_suffix: Option<Arc<str>>,
535 input_border: bool,
536 input_divider: bool,
537 input_divider_style: Style,
538 input_divider_join_frame: bool,
539 input_caret_shape: Option<CaretShape>,
540 input_caret_color: Option<Color>,
541 input_border_style: BorderStyle,
542 input_padding: Padding,
543 input_style: Style,
544 input_hover_style: StyleSlot,
545 input_focus_style: StyleSlot,
546 input_focus_content_style: Style,
547 input_placeholder_style: Style,
548 input_focus_placeholder_style: Style,
549 input_prefix_style: Style,
550 input_focus_prefix_style: Style,
551 input_suffix_style: Style,
552 input_focus_suffix_style: Style,
553 list_config: ListConfig,
555 list_symbol_column: Option<bool>,
556 list_hover_style: StyleSlot,
557 list_active_style: StyleSlot,
558 list_active_symbol: Option<Arc<str>>,
559 list_active_symbol_style: Option<Style>,
560 list_unselected_symbol: Option<Arc<str>>,
561 list_focusable: bool,
562 input_key: Option<Key>,
563 tab_stop: bool,
564 on_focus: Option<Callback<()>>,
565 on_blur: Option<Callback<()>>,
566 empty_text: Option<Arc<str>>,
567 item_style: Style,
569 active_item_style: Option<Style>,
570 header_style: Style,
571 description_style: Style,
572 active_description_style: Option<Style>,
573 focused_description_style: Option<Style>,
574 description_placement: DescriptionPlacement,
575 description_separator: Option<Arc<str>>,
576 description_selection: bool,
577 description_overflow: DescriptionOverflow,
578 primary_truncate_description_first: bool,
580 match_style: Style,
581 show_scores: bool,
582 score_gradient: Option<ColorGradient>,
583 score_range: Option<GradientRange>,
584 preserve_groups: bool,
590 preserve_item_order: bool,
593 navigation_wrap: bool,
594 match_mode: SearchMatchMode,
596 case_matching: CaseMatching,
597 normalization: Normalization,
598 input_key_interceptor: Option<KeyHandler>,
600 on_query_change: Option<Callback<Arc<str>>>,
602 on_select: Option<Callback<SearchEvent<T>>>,
603 on_activate: Option<Callback<SearchEvent<T>>>,
604 render_item: Option<SearchRenderer<T>>,
605 item_status: Option<SearchStatusRenderer<T>>,
606 item_gutter: Option<SearchGutterRenderer<T>>,
607}
608
609impl<T: PartialEq> PartialEq for SearchPaletteProps<T> {
610 fn eq(&self, other: &Self) -> bool {
611 self.items == other.items
612 && self.entries == other.entries
613 && self.sync_match_limit == other.sync_match_limit
614 && self.sync_selection == other.sync_selection
615 && self.initial_query == other.initial_query
616 && self.initial_selected_item_index == other.initial_selected_item_index
617 && self.query == other.query
618 && self.placeholder == other.placeholder
619 && self.width == other.width
620 && self.height == other.height
621 && self.max_width == other.max_width
622 && self.max_height == other.max_height
623 && self.input_prefix == other.input_prefix
624 && self.input_suffix == other.input_suffix
625 && self.input_border == other.input_border
626 && self.input_divider == other.input_divider
627 && self.input_divider_style == other.input_divider_style
628 && self.input_divider_join_frame == other.input_divider_join_frame
629 && self.input_caret_shape == other.input_caret_shape
630 && self.input_caret_color == other.input_caret_color
631 && self.input_border_style == other.input_border_style
632 && self.input_padding == other.input_padding
633 && self.input_style == other.input_style
634 && self.input_hover_style == other.input_hover_style
635 && self.input_focus_style == other.input_focus_style
636 && self.input_placeholder_style == other.input_placeholder_style
637 && self.input_focus_placeholder_style == other.input_focus_placeholder_style
638 && self.input_prefix_style == other.input_prefix_style
639 && self.input_focus_prefix_style == other.input_focus_prefix_style
640 && self.input_suffix_style == other.input_suffix_style
641 && self.input_focus_suffix_style == other.input_focus_suffix_style
642 && self.list_config == other.list_config
643 && self.list_symbol_column == other.list_symbol_column
644 && self.list_hover_style == other.list_hover_style
645 && self.list_active_style == other.list_active_style
646 && self.list_active_symbol == other.list_active_symbol
647 && self.list_active_symbol_style == other.list_active_symbol_style
648 && self.list_unselected_symbol == other.list_unselected_symbol
649 && self.list_focusable == other.list_focusable
650 && self.input_key == other.input_key
651 && self.tab_stop == other.tab_stop
652 && self.on_focus == other.on_focus
653 && self.on_blur == other.on_blur
654 && self.empty_text == other.empty_text
655 && self.item_style == other.item_style
656 && self.active_item_style == other.active_item_style
657 && self.header_style == other.header_style
658 && self.description_style == other.description_style
659 && self.active_description_style == other.active_description_style
660 && self.focused_description_style == other.focused_description_style
661 && self.description_placement == other.description_placement
662 && self.description_separator == other.description_separator
663 && self.description_selection == other.description_selection
664 && self.description_overflow == other.description_overflow
665 && self.primary_truncate_description_first == other.primary_truncate_description_first
666 && self.preserve_groups == other.preserve_groups
667 && self.preserve_item_order == other.preserve_item_order
668 && self.navigation_wrap == other.navigation_wrap
669 && self.match_style == other.match_style
670 && self.show_scores == other.show_scores
671 && self.score_gradient == other.score_gradient
672 && self.score_range == other.score_range
673 && self.match_mode == other.match_mode
674 && self.case_matching == other.case_matching
675 && self.normalization == other.normalization
676 && self.input_key_interceptor.is_some() == other.input_key_interceptor.is_some()
677 && self.on_query_change == other.on_query_change
678 && self.on_select == other.on_select
679 && self.on_activate == other.on_activate
680 && render_item_eq(&self.render_item, &other.render_item)
681 && render_status_eq(&self.item_status, &other.item_status)
682 && render_gutter_eq(&self.item_gutter, &other.item_gutter)
683 }
684}
685
686fn render_item_eq<T>(left: &Option<SearchRenderer<T>>, right: &Option<SearchRenderer<T>>) -> bool {
687 match (left, right) {
688 (Some(left), Some(right)) => Arc::ptr_eq(left, right),
689 (None, None) => true,
690 _ => false,
691 }
692}
693
694fn render_gutter_eq<T>(
695 left: &Option<SearchGutterRenderer<T>>,
696 right: &Option<SearchGutterRenderer<T>>,
697) -> bool {
698 match (left, right) {
699 (Some(left), Some(right)) => Arc::ptr_eq(left, right),
700 (None, None) => true,
701 _ => false,
702 }
703}
704
705fn render_status_eq<T>(
706 left: &Option<SearchStatusRenderer<T>>,
707 right: &Option<SearchStatusRenderer<T>>,
708) -> bool {
709 match (left, right) {
710 (Some(left), Some(right)) => Arc::ptr_eq(left, right),
711 (None, None) => true,
712 _ => false,
713 }
714}
715
716#[derive(Clone)]
766pub struct SearchPalette<T> {
767 props: SearchPaletteProps<T>,
768}
769
770impl<T: Clone + PartialEq> Default for SearchPalette<T> {
771 fn default() -> Self {
772 Self {
773 props: SearchPaletteProps {
774 items: Arc::from([]),
775 entries: Arc::from([]),
776 sync_match_limit: DEFAULT_SYNC_MATCH_LIMIT,
777 sync_selection: false,
778 initial_query: "".into(),
779 initial_selected_item_index: None,
780 query: None,
781 placeholder: "Search...".into(),
782 width: Length::Flex(1),
783 height: Length::Flex(1),
784 max_width: None,
785 max_height: None,
786 input_prefix: None,
787 input_suffix: None,
788 input_border: false,
789 input_divider: true,
790 input_divider_style: Style::default(),
791 input_divider_join_frame: true,
792 input_caret_shape: None,
793 input_caret_color: None,
794 input_border_style: BorderStyle::Plain,
795 input_padding: Padding {
796 left: 1,
797 right: 1,
798 top: 0,
799 bottom: 0,
800 },
801 input_style: Style::default(),
802 input_hover_style: StyleSlot::Inherit,
803 input_focus_style: StyleSlot::Inherit,
804 input_focus_content_style: Style::default(),
805 input_placeholder_style: Style::default(),
806 input_focus_placeholder_style: Style::default(),
807 input_prefix_style: Style::default(),
808 input_focus_prefix_style: Style::default(),
809 input_suffix_style: Style::default(),
810 input_focus_suffix_style: Style::default(),
811 list_config: ListConfig {
812 border: false,
813 border_style: BorderStyle::Plain,
814 padding: Padding::default(),
815 style: Style::default(),
816 selection_style: StyleSlot::Inherit,
817 unfocused_selection_style: StyleSlot::Inherit,
818 selection_full_width: false,
819 selection_symbol: Some("> ".into()),
820 selection_symbol_right: None,
821 selection_symbol_style: None,
822 unfocused_selection_symbol_style: None,
823 symbol_column: true,
824 gutter_gap: 0,
825 gutter_for_non_selectable: false,
826 item_horizontal_padding: Padding::default(),
827 header_horizontal_padding: Padding::default(),
828 empty_text_style: Style::default(),
829 item_hover_style: None,
830 scrollbar: false,
831 scrollbar_config: ScrollbarConfig::default(),
832 },
833 list_symbol_column: None,
834 list_hover_style: StyleSlot::Inherit,
835 list_active_style: StyleSlot::Inherit,
836 list_active_symbol: None,
837 list_active_symbol_style: None,
838 list_unselected_symbol: None,
839 list_focusable: true,
840 input_key: None,
841 tab_stop: true,
842 on_focus: None,
843 on_blur: None,
844 empty_text: Some("No matches".into()),
845 item_style: Style::default(),
846 active_item_style: None,
847 header_style: Style::default(),
848 description_style: Style::default(),
849 active_description_style: None,
850 focused_description_style: None,
851 description_placement: DescriptionPlacement::Inline,
852 description_separator: None,
853 description_selection: true,
854 description_overflow: DescriptionOverflow::Truncate,
855 primary_truncate_description_first: true,
856 match_style: Style::default(),
857 show_scores: false,
858 score_gradient: None,
859 score_range: None,
860 preserve_groups: false,
861 preserve_item_order: false,
862 navigation_wrap: true,
863 match_mode: SearchMatchMode::default(),
864 case_matching: CaseMatching::Smart,
865 normalization: Normalization::Smart,
866 input_key_interceptor: None,
867 on_query_change: None,
868 on_select: None,
869 on_activate: None,
870 render_item: None,
871 item_status: None,
872 item_gutter: None,
873 },
874 }
875 }
876}
877
878impl<T: Clone + PartialEq> SearchPalette<T> {
879 pub fn new() -> Self {
881 Self::default()
882 }
883
884 pub fn items(mut self, items: impl IntoIterator<Item = SearchItem<T>>) -> Self {
886 self.props.items = items.into_iter().collect::<Vec<_>>().into();
887 self.props.entries = Arc::from([]);
888 self
889 }
890
891 pub fn items_arc(mut self, items: Arc<[SearchItem<T>]>) -> Self {
893 self.props.items = items;
894 self.props.entries = Arc::from([]);
895 self
896 }
897
898 pub fn entries(mut self, entries: impl IntoIterator<Item = SearchEntry<T>>) -> Self {
906 let entries: Arc<[SearchEntry<T>]> = entries.into_iter().collect::<Vec<_>>().into();
907 self.props.items = entries
908 .iter()
909 .filter_map(|e| {
910 if let SearchEntry::Item(item) = e {
911 Some(item.clone())
912 } else {
913 None
914 }
915 })
916 .collect::<Vec<_>>()
917 .into();
918 self.props.entries = entries;
919 self
920 }
921
922 pub fn entries_arc(mut self, entries: Arc<[SearchEntry<T>]>) -> Self {
926 self.props.items = entries
927 .iter()
928 .filter_map(|e| {
929 if let SearchEntry::Item(item) = e {
930 Some(item.clone())
931 } else {
932 None
933 }
934 })
935 .collect::<Vec<_>>()
936 .into();
937 self.props.entries = entries;
938 self
939 }
940
941 pub fn placeholder(mut self, placeholder: impl Into<Arc<str>>) -> Self {
943 self.props.placeholder = placeholder.into();
944 self
945 }
946
947 pub fn sync_match_limit(mut self, limit: usize) -> Self {
953 self.props.sync_match_limit = limit;
954 self
955 }
956
957 pub fn sync_selection(mut self, sync: bool) -> Self {
964 self.props.sync_selection = sync;
965 self
966 }
967
968 pub fn initial_query(mut self, query: impl Into<Arc<str>>) -> Self {
972 self.props.initial_query = query.into();
973 self
974 }
975
976 pub fn initial_selected_item_index(mut self, index: Option<usize>) -> Self {
983 self.props.initial_selected_item_index = index;
984 self
985 }
986
987 pub fn navigation_wrap(mut self, wrap: bool) -> Self {
992 self.props.navigation_wrap = wrap;
993 self
994 }
995
996 pub fn query(mut self, query: impl Into<Arc<str>>) -> Self {
1006 self.props.query = Some(query.into());
1007 self
1008 }
1009
1010 pub fn width(mut self, width: Length) -> Self {
1012 self.props.width = width;
1013 self
1014 }
1015
1016 pub fn height(mut self, height: Length) -> Self {
1018 self.props.height = height;
1019 self
1020 }
1021
1022 pub fn max_width(mut self, width: Length) -> Self {
1024 self.props.max_width = Some(width);
1025 self
1026 }
1027
1028 pub fn max_height(mut self, height: Length) -> Self {
1030 self.props.max_height = Some(height);
1031 self
1032 }
1033
1034 pub fn on_query_change(mut self, cb: Callback<Arc<str>>) -> Self {
1036 self.props.on_query_change = Some(cb);
1037 self
1038 }
1039
1040 pub fn on_select(mut self, cb: Callback<SearchEvent<T>>) -> Self {
1047 self.props.on_select = Some(cb);
1048 self
1049 }
1050
1051 pub fn input_key_interceptor(mut self, handler: KeyHandler) -> Self {
1065 self.props.input_key_interceptor = Some(handler);
1066 self
1067 }
1068
1069 pub fn on_activate(mut self, cb: Callback<SearchEvent<T>>) -> Self {
1071 self.props.on_activate = Some(cb);
1072 self
1073 }
1074
1075 pub fn input_prefix(mut self, prefix: impl Into<Arc<str>>) -> Self {
1079 self.props.input_prefix = Some(prefix.into());
1080 self
1081 }
1082
1083 pub fn input_suffix(mut self, suffix: impl Into<Arc<str>>) -> Self {
1085 self.props.input_suffix = Some(suffix.into());
1086 self
1087 }
1088
1089 pub fn input_border(mut self, border: bool) -> Self {
1091 self.props.input_border = border;
1092 self
1093 }
1094
1095 pub fn input_divider(mut self, divider: bool) -> Self {
1099 self.props.input_divider = divider;
1100 self
1101 }
1102
1103 pub fn input_divider_style(mut self, style: Style) -> Self {
1105 self.props.input_divider_style = style;
1106 self
1107 }
1108
1109 pub fn input_divider_join_frame(mut self, join: bool) -> Self {
1113 self.props.input_divider_join_frame = join;
1114 self
1115 }
1116
1117 pub fn input_caret_shape(mut self, shape: CaretShape) -> Self {
1119 self.props.input_caret_shape = Some(shape);
1120 self
1121 }
1122
1123 pub fn input_caret_color(mut self, color: Color) -> Self {
1125 self.props.input_caret_color = Some(color);
1126 self
1127 }
1128
1129 pub fn input_border_style(mut self, border_style: BorderStyle) -> Self {
1131 self.props.input_border_style = border_style;
1132 self
1133 }
1134
1135 pub fn input_padding(mut self, padding: impl Into<Padding>) -> Self {
1137 self.props.input_padding = padding.into();
1138 self
1139 }
1140
1141 pub fn input_style(mut self, style: Style) -> Self {
1143 self.props.input_style = style;
1144 self
1145 }
1146
1147 pub fn input_hover_style(mut self, style: Style) -> Self {
1149 self.props.input_hover_style = StyleSlot::Replace(style);
1150 self
1151 }
1152
1153 pub fn extend_input_hover_style(mut self, style: Style) -> Self {
1155 self.props.input_hover_style = StyleSlot::Extend(style);
1156 self
1157 }
1158
1159 pub fn inherit_input_hover_style(mut self) -> Self {
1161 self.props.input_hover_style = StyleSlot::Inherit;
1162 self
1163 }
1164
1165 pub fn input_hover_style_slot(mut self, slot: StyleSlot) -> Self {
1167 self.props.input_hover_style = slot;
1168 self
1169 }
1170
1171 pub fn input_focus_style(mut self, style: Style) -> Self {
1173 self.props.input_focus_style = StyleSlot::Replace(style);
1174 self
1175 }
1176
1177 pub fn extend_input_focus_style(mut self, style: Style) -> Self {
1179 self.props.input_focus_style = StyleSlot::Extend(style);
1180 self
1181 }
1182
1183 pub fn inherit_input_focus_style(mut self) -> Self {
1185 self.props.input_focus_style = StyleSlot::Inherit;
1186 self
1187 }
1188
1189 pub fn input_focus_style_slot(mut self, slot: StyleSlot) -> Self {
1191 self.props.input_focus_style = slot;
1192 self
1193 }
1194
1195 pub fn input_focus_content_style(mut self, style: Style) -> Self {
1197 self.props.input_focus_content_style = style;
1198 self
1199 }
1200
1201 pub fn input_placeholder_style(mut self, style: Style) -> Self {
1203 self.props.input_placeholder_style = style;
1204 self
1205 }
1206
1207 pub fn input_focus_placeholder_style(mut self, style: Style) -> Self {
1209 self.props.input_focus_placeholder_style = style;
1210 self
1211 }
1212
1213 pub fn input_prefix_style(mut self, style: Style) -> Self {
1215 self.props.input_prefix_style = style;
1216 self
1217 }
1218
1219 pub fn input_focus_prefix_style(mut self, style: Style) -> Self {
1221 self.props.input_focus_prefix_style = style;
1222 self
1223 }
1224
1225 pub fn input_suffix_style(mut self, style: Style) -> Self {
1227 self.props.input_suffix_style = style;
1228 self
1229 }
1230
1231 pub fn input_focus_suffix_style(mut self, style: Style) -> Self {
1233 self.props.input_focus_suffix_style = style;
1234 self
1235 }
1236
1237 pub fn list_config(mut self, config: ListConfig) -> Self {
1241 self.props.list_config = config;
1242 self
1243 }
1244
1245 pub fn list_symbol_column(mut self, enabled: bool) -> Self {
1252 self.props.list_symbol_column = Some(enabled);
1253 self
1254 }
1255
1256 pub fn list_border(mut self, border: bool) -> Self {
1258 self.props.list_config.border = border;
1259 self
1260 }
1261
1262 pub fn list_border_style(mut self, border_style: BorderStyle) -> Self {
1264 self.props.list_config.border_style = border_style;
1265 self
1266 }
1267
1268 pub fn list_padding(mut self, padding: impl Into<Padding>) -> Self {
1270 self.props.list_config.padding = padding.into();
1271 self
1272 }
1273
1274 pub fn list_style(mut self, style: Style) -> Self {
1276 self.props.list_config.style = style;
1277 self
1278 }
1279
1280 pub fn list_hover_style(mut self, style: Style) -> Self {
1282 self.props.list_hover_style = StyleSlot::Replace(style);
1283 self
1284 }
1285
1286 pub fn extend_list_hover_style(mut self, style: Style) -> Self {
1288 self.props.list_hover_style = StyleSlot::Extend(style);
1289 self
1290 }
1291
1292 pub fn inherit_list_hover_style(mut self) -> Self {
1294 self.props.list_hover_style = StyleSlot::Inherit;
1295 self
1296 }
1297
1298 pub fn list_hover_style_slot(mut self, slot: StyleSlot) -> Self {
1300 self.props.list_hover_style = slot;
1301 self
1302 }
1303
1304 pub fn list_selection_style(mut self, style: Style) -> Self {
1306 self.props.list_config.selection_style = StyleSlot::Replace(style);
1307 self
1308 }
1309
1310 pub fn extend_list_selection_style(mut self, style: Style) -> Self {
1312 self.props.list_config.selection_style = StyleSlot::Extend(style);
1313 self
1314 }
1315
1316 pub fn inherit_list_selection_style(mut self) -> Self {
1318 self.props.list_config.selection_style = StyleSlot::Inherit;
1319 self
1320 }
1321
1322 pub fn list_selection_style_slot(mut self, slot: StyleSlot) -> Self {
1324 self.props.list_config.selection_style = slot;
1325 self
1326 }
1327
1328 pub fn list_unfocused_selection_style(mut self, style: Style) -> Self {
1330 self.props.list_config.unfocused_selection_style = StyleSlot::Replace(style);
1331 self
1332 }
1333
1334 pub fn extend_list_unfocused_selection_style(mut self, style: Style) -> Self {
1336 self.props.list_config.unfocused_selection_style = StyleSlot::Extend(style);
1337 self
1338 }
1339
1340 pub fn inherit_list_unfocused_selection_style(mut self) -> Self {
1342 self.props.list_config.unfocused_selection_style = StyleSlot::Inherit;
1343 self
1344 }
1345
1346 pub fn list_unfocused_selection_style_slot(mut self, slot: StyleSlot) -> Self {
1348 self.props.list_config.unfocused_selection_style = slot;
1349 self
1350 }
1351
1352 pub fn list_selection_symbol(mut self, symbol: impl Into<Arc<str>>) -> Self {
1354 self.props.list_config.selection_symbol = Some(symbol.into());
1355 self
1356 }
1357
1358 pub fn list_selection_symbol_right(mut self, symbol: impl Into<Arc<str>>) -> Self {
1361 self.props.list_config.selection_symbol_right = Some(symbol.into());
1362 self
1363 }
1364
1365 pub fn list_selection_symbol_style(mut self, style: Style) -> Self {
1367 self.props.list_config.selection_symbol_style = Some(style);
1368 self
1369 }
1370
1371 pub fn list_unfocused_selection_symbol_style(mut self, style: Style) -> Self {
1373 self.props.list_config.unfocused_selection_symbol_style = Some(style);
1374 self
1375 }
1376
1377 pub fn list_unselected_symbol(mut self, symbol: impl Into<Arc<str>>) -> Self {
1379 self.props.list_unselected_symbol = Some(symbol.into());
1380 self
1381 }
1382
1383 pub fn list_selection_full_width(mut self, full_width: bool) -> Self {
1385 self.props.list_config.selection_full_width = full_width;
1386 self
1387 }
1388
1389 pub fn list_item_hover_style(mut self, style: Style) -> Self {
1391 self.props.list_config.item_hover_style = Some(StyleSlot::Replace(style));
1392 self
1393 }
1394
1395 pub fn extend_list_item_hover_style(mut self, style: Style) -> Self {
1397 self.props.list_config.item_hover_style = Some(StyleSlot::Extend(style));
1398 self
1399 }
1400
1401 pub fn inherit_list_item_hover_style(mut self) -> Self {
1403 self.props.list_config.item_hover_style = Some(StyleSlot::Inherit);
1404 self
1405 }
1406
1407 pub fn list_item_hover_style_slot(mut self, slot: StyleSlot) -> Self {
1409 self.props.list_config.item_hover_style = Some(slot);
1410 self
1411 }
1412
1413 pub fn list_active_style(mut self, style: Style) -> Self {
1415 self.props.list_active_style = StyleSlot::Replace(style);
1416 self
1417 }
1418
1419 pub fn extend_list_active_style(mut self, style: Style) -> Self {
1421 self.props.list_active_style = StyleSlot::Extend(style);
1422 self
1423 }
1424
1425 pub fn inherit_list_active_style(mut self) -> Self {
1427 self.props.list_active_style = StyleSlot::Inherit;
1428 self
1429 }
1430
1431 pub fn list_active_style_slot(mut self, slot: StyleSlot) -> Self {
1433 self.props.list_active_style = slot;
1434 self
1435 }
1436
1437 pub fn list_active_symbol(mut self, symbol: impl Into<Arc<str>>) -> Self {
1439 self.props.list_active_symbol = Some(symbol.into());
1440 self
1441 }
1442
1443 pub fn list_active_symbol_style(mut self, style: Style) -> Self {
1445 self.props.list_active_symbol_style = Some(style);
1446 self
1447 }
1448
1449 pub fn list_item_horizontal_padding(mut self, padding: impl Into<Padding>) -> Self {
1453 self.props.list_config.item_horizontal_padding = padding.into();
1454 self
1455 }
1456
1457 pub fn list_header_horizontal_padding(mut self, padding: impl Into<Padding>) -> Self {
1461 self.props.list_config.header_horizontal_padding = padding.into();
1462 self
1463 }
1464
1465 pub fn list_focusable(mut self, focusable: bool) -> Self {
1467 self.props.list_focusable = focusable;
1468 self
1469 }
1470
1471 pub fn input_key(mut self, key: impl Into<Key>) -> Self {
1486 self.props.input_key = Some(key.into());
1487 self
1488 }
1489
1490 pub fn tab_stop(mut self, tab_stop: bool) -> Self {
1492 self.props.tab_stop = tab_stop;
1493 self
1494 }
1495
1496 pub fn on_focus(mut self, cb: Callback<()>) -> Self {
1498 self.props.on_focus = Some(cb);
1499 self
1500 }
1501
1502 pub fn on_blur(mut self, cb: Callback<()>) -> Self {
1504 self.props.on_blur = Some(cb);
1505 self
1506 }
1507
1508 pub fn list_scrollbar(mut self, scroll: bool) -> Self {
1510 self.props.list_config.scrollbar = scroll;
1511 self
1512 }
1513
1514 pub fn list_scrollbar_config(mut self, config: ScrollbarConfig) -> Self {
1516 self.props.list_config.scrollbar_config = config;
1517 self
1518 }
1519
1520 pub fn empty_text(mut self, text: impl Into<Arc<str>>) -> Self {
1522 self.props.empty_text = Some(text.into());
1523 self
1524 }
1525
1526 pub fn empty_text_style(mut self, style: Style) -> Self {
1528 self.props.list_config.empty_text_style = style;
1529 self
1530 }
1531
1532 pub fn item_style(mut self, style: Style) -> Self {
1536 self.props.item_style = style;
1537 self
1538 }
1539
1540 pub fn header_style(mut self, style: Style) -> Self {
1544 self.props.header_style = style;
1545 self
1546 }
1547
1548 pub fn description_style(mut self, style: Style) -> Self {
1550 self.props.description_style = style;
1551 self
1552 }
1553
1554 pub fn description_placement(mut self, placement: DescriptionPlacement) -> Self {
1556 self.props.description_placement = placement;
1557 self
1558 }
1559
1560 pub fn description_separator(mut self, separator: impl Into<Arc<str>>) -> Self {
1565 self.props.description_separator = Some(separator.into());
1566 self
1567 }
1568
1569 pub fn description_selection(mut self, highlight: bool) -> Self {
1574 self.props.description_selection = highlight;
1575 self
1576 }
1577
1578 pub fn description_overflow(mut self, overflow: DescriptionOverflow) -> Self {
1585 self.props.description_overflow = overflow;
1586 self
1587 }
1588
1589 pub fn primary_truncate_description_first(mut self, truncate: bool) -> Self {
1595 self.props.primary_truncate_description_first = truncate;
1596 self
1597 }
1598
1599 pub fn match_style(mut self, style: Style) -> Self {
1601 self.props.match_style = style;
1602 self
1603 }
1604
1605 pub fn show_scores(mut self, show: bool) -> Self {
1607 self.props.show_scores = show;
1608 self
1609 }
1610
1611 pub fn score_gradient(mut self, gradient: ColorGradient) -> Self {
1613 self.props.score_gradient = Some(gradient);
1614 self
1615 }
1616
1617 pub fn score_range(mut self, min: u64, max: u64) -> Self {
1619 self.props.score_range = Some(GradientRange::new(min, max));
1620 self
1621 }
1622
1623 pub fn preserve_groups(mut self, preserve: bool) -> Self {
1631 self.props.preserve_groups = preserve;
1632 self
1633 }
1634
1635 pub fn preserve_item_order(mut self, preserve: bool) -> Self {
1642 self.props.preserve_item_order = preserve;
1643 self
1644 }
1645
1646 pub fn match_mode(mut self, mode: SearchMatchMode) -> Self {
1651 self.props.match_mode = mode;
1652 self
1653 }
1654
1655 pub fn case_matching(mut self, case: CaseMatching) -> Self {
1657 self.props.case_matching = case;
1658 self
1659 }
1660
1661 pub fn normalization(mut self, normalization: Normalization) -> Self {
1663 self.props.normalization = normalization;
1664 self
1665 }
1666
1667 pub fn active_item_style(mut self, style: Style) -> Self {
1672 self.props.active_item_style = Some(style);
1673 self
1674 }
1675
1676 pub fn active_description_style(mut self, style: Style) -> Self {
1682 self.props.active_description_style = Some(style);
1683 self
1684 }
1685
1686 pub fn focused_description_style(mut self, style: Style) -> Self {
1694 self.props.focused_description_style = Some(style);
1695 self
1696 }
1697
1698 pub fn render_item(mut self, renderer: SearchRenderer<T>) -> Self {
1703 self.props.render_item = Some(renderer);
1704 self
1705 }
1706
1707 pub fn item_status(mut self, renderer: SearchStatusRenderer<T>) -> Self {
1712 self.props.item_status = Some(renderer);
1713 self
1714 }
1715
1716 pub fn item_gutter(mut self, renderer: SearchGutterRenderer<T>) -> Self {
1721 self.props.item_gutter = Some(renderer);
1722 self
1723 }
1724}
1725
1726impl<T: Clone + PartialEq + 'static> From<SearchPalette<T>> for Element {
1727 fn from(palette: SearchPalette<T>) -> Self {
1728 component::element(palette.props)
1729 }
1730}