use crate::app::AppState;
use crate::ui::style::{self, Emphasis};
use crate::palette::command::CommandCategory;
use crate::palette::filter::ScoredCommand;
use crate::config::ThemeConfig;
use ratatui::text::Span;
use ratatui::style::Modifier;
use std::collections::HashMap;
pub fn parse_category_filter(input: &str) -> (String, Option<CommandCategory>) {
if let Some(category_str) = input.strip_prefix("@") {
let category_end = category_str
.char_indices()
.find(|(_, c)| c.is_whitespace())
.map(|(i, _)| i)
.unwrap_or(category_str.len());
let category_name = &category_str[..category_end];
let remaining = category_str[category_end..].trim();
let category = match category_name.to_lowercase().as_str() {
"git" | "gitoperations" => Some(CommandCategory::GitOperations),
"staging" => Some(CommandCategory::Staging),
"remote" => Some(CommandCategory::Remote),
"rebase" => Some(CommandCategory::Rebase),
"reset" => Some(CommandCategory::Reset),
"log" => Some(CommandCategory::Log),
"navigation" | "nav" => Some(CommandCategory::Navigation),
"config" | "configuration" => Some(CommandCategory::Configuration),
"conflicts" | "conflict" => Some(CommandCategory::Conflicts),
"pr" => Some(CommandCategory::Pr),
"autofetch" | "fetch" => Some(CommandCategory::AutoFetch),
"file" | "fileoperations" => Some(CommandCategory::FileOperations),
"merge" | "mergerebase" => Some(CommandCategory::MergeRebase),
_ => None,
};
if category.is_some() {
return (remaining.to_string(), category);
}
}
(input.to_string(), None)
}
pub fn group_by_category<'a>(
commands: &'a [ScoredCommand],
category_filter: Option<CommandCategory>,
) -> HashMap<CommandCategory, Vec<&'a ScoredCommand>> {
let mut grouped: HashMap<CommandCategory, Vec<&ScoredCommand>> = HashMap::new();
for cmd in commands {
if let Some(filter) = category_filter {
if cmd.command.category != filter {
continue;
}
}
grouped
.entry(cmd.command.category)
.or_insert_with(Vec::new)
.push(cmd);
}
for commands_in_category in grouped.values_mut() {
commands_in_category.sort_by(|a, b| b.score.cmp(&a.score));
}
grouped
}
pub fn get_custom_commands(state: &AppState, query: &str) -> Vec<(String, String)> {
let query_lower = query.to_lowercase();
state.custom_commands
.iter()
.filter(|(name, _)| {
query.is_empty() || name.to_lowercase().contains(&query_lower)
})
.map(|(name, cmd)| (name.clone(), cmd.clone()))
.collect()
}
pub fn category_color(category: &CommandCategory, theme: &ThemeConfig) -> ratatui::style::Style {
use CommandCategory::*;
let color = match category {
GitOperations => theme.diff_add_color(),
Staging => theme.staged_color(),
Remote => theme.diff_context_color(),
Rebase | Reset => theme.warning_color(),
Log => theme.muted_color(),
Navigation => theme.border_focused_color(),
Configuration => theme.footer_color(),
Conflicts => theme.error_color(),
AutoFetch => theme.diff_add_color(),
Pr => theme.accent_color(),
FileOperations => theme.diff_context_color(),
MergeRebase => theme.warning_color(),
};
ratatui::style::Style::default().fg(color)
}
pub fn category_abbreviation(category: &CommandCategory) -> &'static str {
use CommandCategory::*;
match category {
GitOperations => "Git",
Staging => "Stg",
Remote => "Rem",
Rebase => "Reb",
Reset => "Rst",
Log => "Log",
Navigation => "Nav",
Configuration => "Cfg",
Conflicts => "Cnf",
AutoFetch => "Fch",
Pr => "PR",
FileOperations => "File",
MergeRebase => "Merge",
}
}
pub fn highlight_matches(
text: &str,
match_ranges: &[(usize, usize)],
state: &AppState,
) -> Vec<Span<'static>> {
if match_ranges.is_empty() {
return vec![Span::styled(
text.to_string(),
style::text(&state.theme, Emphasis::Normal),
)];
}
let mut spans = Vec::new();
let mut last_idx = 0;
let mut sorted_ranges = match_ranges.to_vec();
sorted_ranges.sort_by_key(|r| r.0);
for (start, end) in sorted_ranges {
let start = start.min(text.len());
let end = end.min(text.len());
if start >= end || start > text.len() || end > text.len() {
continue;
}
if start > last_idx {
spans.push(Span::styled(
text[last_idx..start].to_string(),
style::text(&state.theme, Emphasis::Normal),
));
}
spans.push(Span::styled(
text[start..end].to_string(),
style::text(&state.theme, Emphasis::Normal)
.add_modifier(Modifier::BOLD)
.add_modifier(Modifier::UNDERLINED),
));
last_idx = end;
}
if last_idx < text.len() {
spans.push(Span::styled(
text[last_idx..].to_string(),
style::text(&state.theme, Emphasis::Normal),
));
}
spans
}