From 75e032b61d93a391660c1006022cacf1e010aebc Mon Sep 17 00:00:00 2001 From: CodeWhale Bot Date: Tue, 15 Sep 2026 21:59:33 -0700 Subject: [PATCH] perf(tui): lower the settings filter once per pass, not once per row (#6213 T6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `row_matches_filter` lowercased and split `self.filter` on every row, and the settings view runs it twice per interaction — once from `visible_items` and once from `matching_row_indices`. The filter is the same string for every row in a pass, so that work was repeated `2 * rows` times per keystroke. Split the term list into `filter_terms()` and pass it in. Empty terms still mean "everything matches" and the matching expression itself is unchanged, so what the filter accepts is identical. Part of #6213 (item T6). The per-row lowercasing of the row's own label, value, scope and hint — the larger half of this item — is not in this change; those still allocate once per row per pass, and caching them needs the `row_display_value` state dependency settled first, because that one is not a pure function of the row. Verification: cargo check -p codewhale-tui --all-targets --all-features --locked (clean) test result: ok. 329 passed; 0 failed; 0 ignored; 0 measured; 12479 filtered out (tui::views::) Signed-off-by: CodeWhale Bot --- crates/tui/src/tui/views/mod.rs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/crates/tui/src/tui/views/mod.rs b/crates/tui/src/tui/views/mod.rs index 6a8880ed99..99083bed0e 100644 --- a/crates/tui/src/tui/views/mod.rs +++ b/crates/tui/src/tui/views/mod.rs @@ -2713,9 +2713,19 @@ impl ConfigView { if cached == 0 { 8 } else { cached } } - fn row_matches_filter(&self, row: &ConfigRow) -> bool { - let filter = self.filter.trim().to_lowercase(); - if filter.is_empty() { + /// The lowercased search terms for the current filter, computed once per + /// interaction instead of once per row per pass (#6213 T6). + fn filter_terms(&self) -> Vec { + self.filter + .trim() + .to_lowercase() + .split_whitespace() + .map(str::to_string) + .collect() + } + + fn row_matches_filter(&self, row: &ConfigRow, terms: &[String]) -> bool { + if terms.is_empty() { return true; } @@ -2733,7 +2743,7 @@ impl ConfigView { let scope_en = row.scope.label(Locale::En).to_lowercase(); let hint = config_hint_for_key(self.locale, &row.key).to_lowercase(); - filter.split_whitespace().all(|term| { + terms.iter().all(|term| { section.contains(term) || section_en.contains(term) || category_label.contains(term) @@ -2750,11 +2760,12 @@ impl ConfigView { fn matching_row_indices(&self) -> Vec { let filtering = !self.filter.is_empty(); + let terms = self.filter_terms(); self.rows .iter() .enumerate() .filter_map(|(idx, row)| { - (self.row_matches_filter(row) && (filtering || self.category.contains(row))) + (self.row_matches_filter(row, &terms) && (filtering || self.category.contains(row))) .then_some(idx) }) .collect() @@ -2765,8 +2776,9 @@ impl ConfigView { let mut current_section = None; let filtering = !self.filter.is_empty(); + let terms = self.filter_terms(); for (idx, row) in self.rows.iter().enumerate() { - if !self.row_matches_filter(row) { + if !self.row_matches_filter(row, &terms) { continue; } // The rail category filters rows unless the user is searching.