gitbutlerapp/gitbutler · error
FuzzyPickerItem::columns must return a searchable column
Error message
FuzzyPickerItem::columns must return a searchable column
What it means
Same FuzzyPickerItem contract, enforced at query time: once the user types a query, the picker iterates every item and calls .find(|col| col.searchable.is_some()).expect(...). Unlike the layout-time check at fuzzy_picker.rs:114 (which inspects only items[0]), this one covers all items, so it fires on the first keystroke when any item other than the first lacks a searchable column.
Source
Thrown at crates/but/src/command/legacy/status/tui/fuzzy_picker.rs:271
if query.is_empty() {
self.items_to_show.extend(
self.items
.iter()
.enumerate()
.map(|(item_idx, _)| ItemToShow::Plain { item_idx }),
);
} else {
let mut fuzzy_matches = self
.items
.iter()
.enumerate()
.filter_map(|(item_idx, item)| {
let col = item
.columns(SearchableToken(()))
.into_iter()
.find(|col| col.searchable.is_some())
.expect("FuzzyPickerItem::columns must return a searchable column");
let (score, indices) = self.matcher.fuzzy_indices(&col.text, query)?;
Some((item_idx, col, score, indices))
})
.collect::<Vec<_>>();
fuzzy_matches.sort_unstable_by(|(_, _, score_a, _), (_, _, score_b, _)| {
score_a.cmp(score_b).reverse()
});
self.items_to_show.extend(fuzzy_matches.into_iter().map(
|(item_idx, _, _, indices)| ItemToShow::FuzzyMatch {
item_idx,
char_indices: indices,
},
));
}
}
pub fn handle_message(
mut self,View on GitHub (pinned to 2497b8007a)
Solutions
- Make every columns impl unconditionally mark one column searchable; use empty text rather than None when there is nothing to search
- Filter out non-searchable items before passing the list to the picker
- Soften the query path: treat a missing searchable column as unmatchable (return None from the filter_map) instead of expecting
- Add a test that types a query over every item kind used in pickers
Example fix
// before — first keystroke on a non-conforming item panics
let col = item.columns(SearchableToken(())).into_iter()
.find(|col| col.searchable.is_some())
.expect("FuzzyPickerItem::columns must return a searchable column");
// after — non-conforming items are simply unmatchable
let Some(col) = item.columns(SearchableToken(())).into_iter()
.find(|col| col.searchable.is_some()) else { return None }; Defensive patterns
Strategy: validation
Validate before calling
// validate ALL items, not just the first, before the picker starts filtering
let all_searchable = items.iter().all(|i|
i.columns(SearchableToken(())).into_iter().any(|c| c.searchable.is_some())
);
debug_assert!(all_searchable, "item without searchable column in picker"); Type guard
fn is_fuzzy_searchable(item: &impl FuzzyPickerItem) -> bool {
item.columns(SearchableToken(()))
.into_iter()
.any(|col| col.searchable.is_some())
} Prevention
- The layout-time check only inspects items[0] — test with heterogeneous lists where later items differ from the first
- Make columns() impls unconditional: return searchable: Some even for empty/placeholder text
- Type a query in a test over every item kind; the panic fires on first keystroke, not on open
- Consider skipping non-conforming items during filtering instead of expecting
When it happens
Trigger: Typing into the fuzzy picker when the list is heterogeneous and some item/variant's columns impl yields no searchable column (header- or separator-like items mixed with real items), or an impl whose searchable column is conditional on item state.
Common situations: Mixed item lists passed to one picker; items in a state (unnamed branch/stack, empty field) where the impl returns all searchable: None; refactoring columns() and dropping the token; item added after the picker was laid out fine (first item still conforms).
Related errors
- no searchable columns
- line should have been highlighted by now
- symbols must always be initialized
- Failed to persist action: {e}
- Failed to list actions: {e}
AI-assisted analysis of gitbutlerapp/gitbutler@2497b8007a (2026-08-17).
Data as JSON: /api/errors/c935a3d2dec9ae48.
Report an issue: GitHub.