diff --git a/src/tui/steps/commit.rs b/src/tui/steps/commit.rs index 079b4c9..3a802e3 100644 --- a/src/tui/steps/commit.rs +++ b/src/tui/steps/commit.rs @@ -2,7 +2,7 @@ use std::io::Stderr; use promptuity::{prompts::SelectOption, Promptuity}; -use crate::tui::widgets::Autocomplete; +use crate::tui::widgets::{Autocomplete, AutocompletePriority}; use crate::{ config::SimpleCommitsConfig, tui::{structs::COMMIT_TYPES, Step, StepResult}, @@ -20,10 +20,11 @@ impl Step for _Step { ) -> StepResult { let commit = p.prompt(&mut Autocomplete::new( "Select a type", + true, + AutocompletePriority::Label, COMMIT_TYPES .map(|c| SelectOption::new(c, c.label.to_owned()).with_hint(c.hint)) .to_vec(), - true, )); state._type = commit?; diff --git a/src/tui/steps/emoji.rs b/src/tui/steps/emoji.rs index 415a9d3..76f84c5 100644 --- a/src/tui/steps/emoji.rs +++ b/src/tui/steps/emoji.rs @@ -2,7 +2,7 @@ use promptuity::prompts::SelectOption; use crate::config::SimpleCommitsConfig; use crate::gen::EMOJIS; -use crate::tui::widgets::Autocomplete; +use crate::tui::widgets::{Autocomplete, AutocompletePriority}; use crate::tui::{Step, StepResult}; #[derive(Default)] @@ -29,8 +29,9 @@ impl Step for _Step { .to_vec(); let emoji = p.prompt(&mut Autocomplete::new( "Select an emoji (optional)", - emojis_mapped, false, + AutocompletePriority::Hint, + emojis_mapped, ))?; state.emoji = Some(emoji); Ok(()) diff --git a/src/tui/steps/scopes.rs b/src/tui/steps/scopes.rs index 0553084..08b0163 100644 --- a/src/tui/steps/scopes.rs +++ b/src/tui/steps/scopes.rs @@ -3,7 +3,10 @@ use promptuity::prompts::SelectOption; use crate::{ config::SimpleCommitsConfig, - tui::{widgets::Autocomplete, Step, StepResult}, + tui::{ + widgets::{Autocomplete, AutocompletePriority}, + Step, StepResult, + }, }; #[derive(Default)] @@ -27,8 +30,9 @@ impl Step for _Step { .collect::>(); let scope = p.prompt(&mut Autocomplete::new( "Select an scope", - mapped_scopes, false, + AutocompletePriority::Label, + mapped_scopes, ))?; let scope = (!scope.is_empty()).then_some(scope); diff --git a/src/tui/widgets.rs b/src/tui/widgets.rs index ace56f0..18c00be 100644 --- a/src/tui/widgets.rs +++ b/src/tui/widgets.rs @@ -1,3 +1,3 @@ mod autocompleter; -pub use autocompleter::Autocomplete; +pub use autocompleter::{Autocomplete, AutocompletePriority}; diff --git a/src/tui/widgets/autocompleter.rs b/src/tui/widgets/autocompleter.rs index b8e7b13..a906e63 100644 --- a/src/tui/widgets/autocompleter.rs +++ b/src/tui/widgets/autocompleter.rs @@ -5,24 +5,42 @@ use promptuity::pagination::paginate; use promptuity::prompts::{DefaultSelectFormatter, SelectFormatter, SelectOption}; use promptuity::style::*; use promptuity::{Error, InputCursor, Prompt, PromptBody, PromptInput, PromptState, RenderPayload}; + pub struct Autocomplete { formatter: DefaultSelectFormatter, message: String, page_size: usize, options: Vec>, - filtered_options: Vec, + filtered_options: Vec<(usize, i64)>, index: usize, input: InputCursor, matcher: SkimMatcherV2, + priority: AutocompletePriority, strict: bool, skip: bool, } +#[derive(Clone, Copy)] +pub enum AutocompletePriority { + Hint, + Label, +} + +impl From for (i64, i64) { + fn from(value: AutocompletePriority) -> (i64, i64) { + match value { + AutocompletePriority::Hint => (1, 4), + AutocompletePriority::Label => (4, 1), + } + } +} + impl Autocomplete { pub fn new( message: impl std::fmt::Display, - options: Vec>, strict: bool, + priority: AutocompletePriority, + options: Vec>, ) -> Self { Self { formatter: DefaultSelectFormatter::new(), @@ -33,6 +51,7 @@ impl Autocomplete { index: 0, input: InputCursor::default(), matcher: SkimMatcherV2::default(), + priority, strict, skip: false, } @@ -40,6 +59,7 @@ impl Autocomplete { fn run_filter(&mut self) { let pattern = self.input.value(); + let (priority_label, priority_hint): (i64, i64) = self.priority.into(); self.filtered_options = self .options @@ -48,19 +68,38 @@ impl Autocomplete { .filter_map(|(i, option)| { let label = &option.label; let hint = option.hint.clone().unwrap_or_default(); - self.matcher - .fuzzy_match(&format!("{label} {hint}"), &pattern) - .map(|_| i) + let a = self + .matcher + .fuzzy_match(label, &pattern) + .unwrap_or_default(); + let b = self + .matcher + .fuzzy_match(&hint, &pattern) + .unwrap_or_default(); + + let c = (a.saturating_mul(priority_label)) + .saturating_add(b.saturating_mul(priority_hint)) + .saturating_sub(i as i64); + + log::trace!("{pattern} -> {label}; {a} & {b} = {c}"); + if c <= 0 && !pattern.is_empty() { + return None; + } + + Some((i, c)) }) .collect::>(); + self.filtered_options.sort_by_key(|(_, s)| *s); + self.filtered_options.reverse(); + self.index = std::cmp::min(self.filtered_options.len().saturating_sub(1), self.index); } fn current_option(&self) -> Option<&SelectOption> { self.filtered_options .get(self.index) - .and_then(|idx| self.options.get(*idx)) + .and_then(|(idx, _)| self.options.get(*idx)) } } @@ -78,7 +117,7 @@ impl Prompt for Autocomplete { return Err(Error::Config("options cannot be empty.".into())); } - self.filtered_options = (0..self.options.len()).collect(); + self.filtered_options = (0..self.options.len()).map(|i| (i, 0)).collect(); Ok(()) } @@ -197,7 +236,7 @@ impl Prompt for Autocomplete { .items .iter() .enumerate() - .map(|(i, idx)| { + .map(|(i, (idx, _))| { let option = self.options.get(*idx).unwrap(); let active = i == page.cursor; self.formatter.option(