Skip to content

Commit 1168fc0

Browse files
chore[tui]: fuzzy search column names (#4729)
Signed-off-by: Joe Isaacs <[email protected]>
1 parent b6c23b6 commit 1168fc0

File tree

4 files changed

+81
-39
lines changed

4 files changed

+81
-39
lines changed

Cargo.lock

Lines changed: 16 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ fastlanes = "0.2.2"
106106
flatbuffers = "25.2.10"
107107
fsst-rs = "0.5.2"
108108
futures = { version = "0.3.31", default-features = false }
109+
fuzzy-matcher = "0.3"
109110
glob = "0.3.2"
110111
goldenfile = "1"
111112
half = { version = "2.6", features = ["std", "num-traits"] }

vortex-tui/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ crossterm = { workspace = true }
2020
env_logger = { version = "0.11" }
2121
flatbuffers = { workspace = true }
2222
futures = { workspace = true, features = ["executor"] }
23+
fuzzy-matcher = { workspace = true }
2324
humansize = { workspace = true }
2425
indicatif = { workspace = true, features = ["futures"] }
2526
itertools = { workspace = true }

vortex-tui/src/browse/ui/layouts.rs

Lines changed: 63 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
// SPDX-License-Identifier: Apache-2.0
22
// SPDX-FileCopyrightText: Copyright the Vortex contributors
33

4+
use fuzzy_matcher::FuzzyMatcher;
5+
use fuzzy_matcher::skim::SkimMatcherV2;
46
use humansize::{DECIMAL, make_format};
7+
use itertools::Itertools;
58
use ratatui::buffer::Buffer;
69
use ratatui::layout::{Constraint, Direction, Layout, Rect};
710
use ratatui::style::{Color, Style, Stylize};
@@ -218,44 +221,71 @@ fn render_children_list(app: &mut AppState, area: Rect, buf: &mut Buffer) {
218221
let layout = app.cursor.layout();
219222

220223
if layout.nchildren() > 0 {
221-
let filter: Vec<bool> = layout
222-
.child_names()
223-
.map(|name| {
224-
if search_filter.is_empty() {
225-
true
226-
} else {
227-
name.contains(&search_filter)
228-
}
229-
})
230-
.collect();
231-
232-
let list_items: Vec<String> = layout
233-
.child_names()
234-
.zip(filter.iter())
235-
.filter_map(|(name, keep)| keep.then_some(name.to_string()))
236-
.collect();
224+
if search_filter.is_empty() {
225+
// No search filter, show all items
226+
let list_items = layout
227+
.child_names()
228+
.map(|name| name.to_string())
229+
.collect_vec();
230+
231+
app.filter = None;
232+
render_child_list_items(app, area, buf, list_items);
233+
} else {
234+
// Use fuzzy matching to rank and filter results
235+
let matcher = SkimMatcherV2::default();
236+
237+
// Collect scored matches
238+
let mut scored_matches = layout
239+
.child_names()
240+
.enumerate()
241+
.filter_map(|(idx, name)| {
242+
matcher
243+
.fuzzy_match(&name, &search_filter)
244+
.map(|score| (idx, name.to_string(), score))
245+
})
246+
.collect_vec();
247+
248+
// Sort by score (higher is better)
249+
scored_matches.sort_by(|a, b| b.2.cmp(&a.2));
250+
251+
// Create filter based on fuzzy matches
252+
let mut filter = vec![false; layout.nchildren()];
253+
let list_items = scored_matches
254+
.iter()
255+
.map(|(idx, name, _score)| {
256+
filter[*idx] = true;
257+
name.clone()
258+
})
259+
.collect_vec();
237260

238-
if !app.search_filter.is_empty() {
239261
app.filter = Some(filter);
262+
render_child_list_items(app, area, buf, list_items);
240263
}
264+
}
265+
}
241266

242-
let container = Block::new()
243-
.title("Child Layouts")
244-
.borders(Borders::ALL)
245-
.border_type(BorderType::Rounded)
246-
.border_style(Style::default().fg(Color::DarkGray));
267+
fn render_child_list_items(
268+
app: &mut AppState,
269+
area: Rect,
270+
buf: &mut Buffer,
271+
list_items: Vec<String>,
272+
) {
273+
let container = Block::new()
274+
.title("Child Layouts")
275+
.borders(Borders::ALL)
276+
.border_type(BorderType::Rounded)
277+
.border_style(Style::default().fg(Color::DarkGray));
247278

248-
let inner_area = container.inner(area);
279+
let inner_area = container.inner(area);
249280

250-
container.render(area, buf);
281+
container.render(area, buf);
251282

252-
// Render the List view.
253-
// TODO: add state so we can scroll
254-
StatefulWidget::render(
255-
List::new(list_items).highlight_style(Style::default().black().on_white().bold()),
256-
inner_area,
257-
buf,
258-
&mut app.layouts_list_state,
259-
);
260-
}
283+
// Render the List view.
284+
// TODO: add state so we can scroll
285+
StatefulWidget::render(
286+
List::new(list_items).highlight_style(Style::default().black().on_white().bold()),
287+
inner_area,
288+
buf,
289+
&mut app.layouts_list_state,
290+
);
261291
}

0 commit comments

Comments
 (0)