gui: spec-blind suite; search finds the best match, axis ticks never overflow
tests/spec_gui.rs (31 spec-blind tests): exact money text round-trips, file names never escape the folder or hit a Windows device name, history against a browser model, fuzzy search against a subsequence model, axis ticks, the view model against ledger-core, and the attention list. Found and fixed: - fuzzy matched greedily, so "ac c" scored 1 for "c" (the "c" inside "ac") instead of 5 for the word "c"; it now scores the best alignment. - ticks accumulated float error (a last tick at -5.55e-17 for a top of 0), gave more than n + 2 ticks at large magnitudes with tiny spans, and overflowed for spans near f64's limit. Ticks are now whole multiples of the step, checked, with [lo, hi] when f64 has no nice step. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
This commit is contained in:
1 parent
714caf9be9
commit
2c4af07611
7 files changed
+1328
-53
No files matched your search
@@ -157,42 +157,58 @@ impl Palette {
|
||||
/// query matches everything with score 0.
|
||||
/// 2. A match's score is at least the number of non-space query
|
||||
/// characters; consecutive matches and matches at a word start score
|
||||
/// more, so a word-start prefix scores at least as high as any
|
||||
/// other match of the same query.
|
||||
/// more. The score is the best over every way to match (not the
|
||||
/// first match found), so a hay holding the query whole at a word
|
||||
/// start scores at least as high as any hay's match of it.
|
||||
/// ```
|
||||
fn fuzzy(hay: &str, query: &str) -> Option<i32> {
|
||||
if query.is_empty() {
|
||||
pub fn fuzzy(hay: &str, query: &str) -> Option<i32> {
|
||||
let hay: Vec<char> = hay.to_lowercase().chars().collect();
|
||||
// Query characters, each marked whether a space came just before it
|
||||
// (a space ends a run: the next match earns no run bonus).
|
||||
let mut q: Vec<(char, bool)> = Vec::new();
|
||||
let mut after_space = false;
|
||||
for c in query.to_lowercase().chars() {
|
||||
if c == ' ' {
|
||||
after_space = true;
|
||||
} else {
|
||||
q.push((c, after_space));
|
||||
after_space = false;
|
||||
}
|
||||
}
|
||||
if q.is_empty() {
|
||||
return Some(0);
|
||||
}
|
||||
let hay: Vec<char> = hay.to_lowercase().chars().collect();
|
||||
let mut score = 0;
|
||||
let mut hi = 0;
|
||||
let mut prev_match = false;
|
||||
for qc in query.to_lowercase().chars() {
|
||||
if qc == ' ' {
|
||||
prev_match = false;
|
||||
continue;
|
||||
}
|
||||
let mut found = false;
|
||||
while hi < hay.len() {
|
||||
let c = hay[hi];
|
||||
hi += 1;
|
||||
if c == qc {
|
||||
score += if prev_match { 6 } else { 1 };
|
||||
if hi == 1 || !hay[hi - 2].is_alphanumeric() {
|
||||
score += 4;
|
||||
}
|
||||
prev_match = true;
|
||||
found = true;
|
||||
break;
|
||||
// The best score over every way to match (a greedy left-most match
|
||||
// can miss a word start further on): best[j] is the top score for the
|
||||
// query so far with its last character at hay[j].
|
||||
let word_start = |j: usize| j == 0 || !hay[j - 1].is_alphanumeric();
|
||||
let mut best: Vec<Option<i32>> = hay
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(j, &c)| (c == q[0].0).then(|| 1 + if word_start(j) { 4 } else { 0 }))
|
||||
.collect();
|
||||
for &(qc, gap) in &q[1..] {
|
||||
let mut next = vec![None; hay.len()];
|
||||
// The best score ending strictly before j.
|
||||
let mut before: Option<i32> = None;
|
||||
for j in 0..hay.len() {
|
||||
if hay[j] == qc {
|
||||
let bonus = if word_start(j) { 4 } else { 0 };
|
||||
let run = if gap {
|
||||
None
|
||||
} else {
|
||||
j.checked_sub(1)
|
||||
.and_then(|p| best[p])
|
||||
.map(|s| s + 6 + bonus)
|
||||
};
|
||||
let jump = before.map(|s| s + 1 + bonus);
|
||||
next[j] = run.max(jump);
|
||||
}
|
||||
prev_match = false;
|
||||
}
|
||||
if !found {
|
||||
return None;
|
||||
before = before.max(best[j]);
|
||||
}
|
||||
best = next;
|
||||
}
|
||||
Some(score)
|
||||
best.into_iter().flatten().max()
|
||||
}
|
||||
|
||||
fn search(b: &Books, query: &str) -> Vec<Item> {
|
||||
|
||||
Reference in new issue
Block a user