use crate::config::{Entry, PathEntry, VarKind};
use crate::tui::state::{AppState, EditorStyle, VarTypeOption};
use ratatui::widgets::ListState;
#[derive(Clone)]
pub struct VarRow {
pub name: String,
pub kind: VarKind,
pub separator: String,
pub count: usize,
}
pub fn clamp_list_state(state: &mut ListState, len: usize) {
if len == 0 {
state.select(None);
return;
}
let selected = state.selected().unwrap_or(0);
let next = selected.min(len.saturating_sub(1));
state.select(Some(next));
}
pub fn compute_var_rows(app: &AppState) -> Vec<VarRow> {
let profile = &app.profiles[app.active_profile_index];
let mut rows: Vec<VarRow> = app
.var_options
.iter()
.map(|o| {
let count = profile
.entries
.iter()
.filter(|e| e.var_name().as_ref() == o.name)
.count();
VarRow {
name: o.name.clone(),
kind: o.kind.clone(),
separator: o.separator.clone(),
count,
}
})
.collect();
for e in &profile.entries {
let name = e.var_name().into_owned();
if rows.iter().any(|r| r.name == name) {
continue;
}
rows.push(VarRow {
name: name.clone(),
kind: VarKind::List,
separator: e.separator().into_owned(),
count: profile
.entries
.iter()
.filter(|x| x.var_name().as_ref() == name)
.count(),
});
}
rows.sort_by(|a, b| a.name.cmp(&b.name));
if !app.vars_filter.is_empty() {
let q = app.vars_filter.to_lowercase();
rows.retain(|r| r.name.to_lowercase().contains(&q));
}
rows
}
pub fn current_var_parts(app: &AppState, var_name: &str) -> Vec<Entry> {
let profile = &app.profiles[app.active_profile_index];
profile
.entries
.iter()
.filter(|e| e.var_name().as_ref() == var_name)
.cloned()
.collect()
}
pub fn visible_part_indices(app: &AppState, parts: &[Entry]) -> Vec<usize> {
let mut indices: Vec<usize> = (0..parts.len()).collect();
if !app.parts_filter.is_empty() {
let q = app.parts_filter.to_lowercase();
indices.retain(|i| parts[*i].to_string().to_lowercase().contains(&q));
}
indices
}
pub fn visible_item_indices(app: &AppState) -> Vec<usize> {
let mut indices: Vec<usize> = (0..app.items.len()).collect();
if !app.items_filter.is_empty() {
let q = app.items_filter.to_lowercase();
indices.retain(|i| {
let it = &app.items[*i];
it.value.to_lowercase().contains(&q)
|| it.tags.iter().any(|t| t.to_lowercase().contains(&q))
});
}
indices
}
pub fn visible_profile_indices(app: &AppState) -> Vec<usize> {
let mut indices: Vec<usize> = (0..app.profiles.len()).collect();
if !app.profiles_filter.is_empty() {
let q = app.profiles_filter.to_lowercase();
indices.retain(|i| app.profiles[*i].name.to_lowercase().contains(&q));
}
indices
}
pub fn selected_profile_index(app: &AppState) -> Option<usize> {
let indices = visible_profile_indices(app);
app.profile_list_state
.selected()
.and_then(|i| indices.get(i).copied())
}
pub fn selected_item_index(app: &AppState) -> Option<usize> {
let indices = visible_item_indices(app);
app.items_list_state
.selected()
.and_then(|i| indices.get(i).copied())
}
pub fn preview_value(e: &Entry) -> String {
match e {
Entry::Path(PathEntry { path, .. }) => path.clone(),
Entry::CPath(v)
| Entry::CInclude(v)
| Entry::CPlusInclude(v)
| Entry::OBJCInclude(v)
| Entry::CPPFlag(v)
| Entry::CFlag(v)
| Entry::CXXFlag(v)
| Entry::LDFlag(v)
| Entry::LibraryPath(v)
| Entry::LDLibraryPath(v)
| Entry::LDRunPath(v)
| Entry::RanLib(v)
| Entry::CC(v)
| Entry::CXX(v)
| Entry::AR(v)
| Entry::Strip(v)
| Entry::GCCExecPrefix(v)
| Entry::CollectGCCOptions(v)
| Entry::Lang(v) => v.clone(),
Entry::CustomScalar { value, .. } => value.clone(),
Entry::CustomPart { value, .. } => value.clone(),
}
}
pub fn make_part_entry(app: &AppState, var_name: &str, value: String) -> Option<Entry> {
match var_name {
"PATH" => {
return Some(Entry::Path(PathEntry {
path: value,
program: String::new(),
version: String::new(),
}))
}
"CPATH" => return Some(Entry::CPath(value)),
"C_INCLUDE_PATH" => return Some(Entry::CInclude(value)),
"CPLUS_INCLUDE_PATH" => return Some(Entry::CPlusInclude(value)),
"OBJC_INCLUDE_PATH" => return Some(Entry::OBJCInclude(value)),
"CPPFLAGS" => return Some(Entry::CPPFlag(value)),
"CFLAGS" => return Some(Entry::CFlag(value)),
"CXXFLAGS" => return Some(Entry::CXXFlag(value)),
"LDFLAGS" => return Some(Entry::LDFlag(value)),
"LIBRARY_PATH" => return Some(Entry::LibraryPath(value)),
"LD_LIBRARY_PATH" => return Some(Entry::LDLibraryPath(value)),
"LD_RUN_PATH" => return Some(Entry::LDRunPath(value)),
"RANLIB" => return Some(Entry::RanLib(value)),
"CC" => return Some(Entry::CC(value)),
"CXX" => return Some(Entry::CXX(value)),
"AR" => return Some(Entry::AR(value)),
"STRIP" => return Some(Entry::Strip(value)),
"GCC_EXEC_PREFIX" => return Some(Entry::GCCExecPrefix(value)),
"COLLECT_GCC_OPTIONS" => return Some(Entry::CollectGCCOptions(value)),
"LANG" => return Some(Entry::Lang(value)),
_ => {}
}
if let Some(def) = app.custom_var_defs.iter().find(|d| d.name == var_name) {
return match def.kind {
VarKind::Scalar => Some(Entry::CustomScalar {
name: def.name.clone(),
value,
}),
VarKind::List => Some(Entry::CustomPart {
name: def.name.clone(),
value,
separator: def.separator.clone(),
}),
};
}
Some(Entry::CustomPart {
name: var_name.to_string(),
value,
separator: ":".to_string(),
})
}
pub fn var_option_for(app: &AppState, var_name: &str) -> VarTypeOption {
if let Some(o) = app.var_options.iter().find(|o| o.name == var_name) {
return o.clone();
}
VarTypeOption {
name: var_name.to_string(),
kind: VarKind::List,
separator: ":".to_string(),
editor: EditorStyle::PartsList,
}
}