chadtmiller.com/knotless
git that lives in your Atmosphere account
1
10
A code search's pages go on from the one before
crates/knotless-serve/src/web/search.rs+4−4
| @@ -7,8 +7,8 @@ use serde_json::{Value, json}; | ||
/// How many files a search shows, if not asked, and at most. | ||
const FILES: usize = 20; | ||
const FILES_MAX: usize = 50; | ||
/// How deep the pages of a search go: Zoekt answers the best files from | ||
/// the first, so each page asks again for those before it. | ||
/// How deep the pages of a search go: each page asks Zoekt for all of | ||
/// them, as it cuts off files it scores alike as they come. | ||
const DEEPEST: usize = 200; | ||
| ||
#[derive(Deserialize)] | ||
| @@ -75,11 +75,11 @@ pub async fn search_code( | ||
_ => Vec::new(), | ||
} | ||
}; | ||
let (found, faceted) = tokio::join!(code.search(&p.q, within, upto), faceted); | ||
let (found, faceted) = tokio::join!(code.search(&p.q, within, DEEPEST), faceted); | ||
let found = found?; | ||
let more = upto < DEEPEST && found.file_count > upto as u64; | ||
let listed = |h: crate::code::Hit| Some((index.listed(&h.did, &h.key).ok().flatten()?, h)); | ||
let pages_before = found.hits.into_iter().skip(skip); | ||
let pages_before = found.hits.into_iter().take(upto).skip(skip); | ||
let shown: Vec<_> = pages_before.filter_map(listed).collect(); | ||
let owners = shown.iter().map(|(r, _)| actor(&app, &r.did)); | ||
let owners = futures::future::join_all(owners).await; | ||
crates/knotless-serve/src/code.rs+16−6
| @@ -113,6 +113,8 @@ struct ZResult { | ||
#[derive(Deserialize)] | ||
#[serde(rename_all = "PascalCase")] | ||
struct ZFile { | ||
#[serde(default)] | ||
score: f64, | ||
file_name: String, | ||
repository: String, | ||
language: String, | ||
| @@ -278,12 +280,18 @@ impl Code { | ||
"MaxWallTime": WALL.as_nanos() as u64, | ||
}); | ||
let found = self.ask(within(q, repo), opts).await?; | ||
let hits = found | ||
.files | ||
.unwrap_or_default() | ||
.into_iter() | ||
.filter_map(hit) | ||
.collect(); | ||
let mut files = found.files.unwrap_or_default(); | ||
// The best first, and files Zoekt scores alike, as its shards | ||
// answered in parallel, by repository and path: so each page | ||
// after the first goes on from it rather than showing it again. | ||
files.sort_by(|a, b| { | ||
let named = a | ||
.repository | ||
.cmp(&b.repository) | ||
.then(a.file_name.cmp(&b.file_name)); | ||
b.score.total_cmp(&a.score).then(named) | ||
}); | ||
let hits = files.into_iter().filter_map(hit).collect(); | ||
Ok(Results { | ||
hits, | ||
file_count: found.file_count, | ||
| @@ -599,6 +607,7 @@ mod tests { | ||
file_name: false, | ||
}; | ||
let found = hit(ZFile { | ||
score: 0.0, | ||
file_name: "a".into(), | ||
repository: "did:plc:a/k".into(), | ||
language: String::new(), | ||
| @@ -610,6 +619,7 @@ mod tests { | ||
assert_eq!(lines, [2, 30]); | ||
assert!( | ||
hit(ZFile { | ||
score: 0.0, | ||
file_name: "a".into(), | ||
repository: "no-slash".into(), | ||
language: String::new(), | ||
docs/design/serve.md+7−5
| @@ -442,11 +442,13 @@ query, run at the same time: the same one with its `lang:` filters taken | ||
out, asking only for files (`type:file`), up to 1000 of them. Picking a | ||
language then leaves the others listed. | ||
| ||
Zoekt ranks the files it finds but has no cursor, so a page after the | ||
first asks it for the best files up to that page's end, and leaves out | ||
those the pages before held. Each page costs a little more than the one | ||
before, so the pages stop 200 files deep: a search that finds more is | ||
narrowed rather than read further. Only the first page asks for the | ||
Zoekt ranks the files it finds but has no cursor, and files it scores | ||
alike come back in whatever order its shards answered. So each page asks | ||
it for the best 200 files, sorts those it scores alike by repository and | ||
path, and answers its own slice of them: a page goes on from the one | ||
before rather than showing a file again or passing one over. The pages | ||
stop 200 files deep: a search that finds more is narrowed rather than | ||
read further. Only the first page asks for the | ||
languages. | ||
| ||
### Shared steps | ||
docs/reference/api.md+2−1
| @@ -164,7 +164,8 @@ alone. | ||
| `limit` | 20 | the most files answered, clamped to 1 to 50 | | ||
| `cursor` | | from the page before: how many files it held | | ||
| ||
Answers `files`, the best first, each with its `repo` (as `listRepos` | ||
Answers `files`, the best first, and those found alike by repository and | ||
path, each with its `repo` (as `listRepos` | ||
answers one), `path`, `language?`, `commit?` (the commit of the default | ||
branch that was indexed, which its lines are of) and `chunks`: runs of lines holding | ||
matches, each its `startLine`, its `content` and its `ranges`, a match | ||