records: a phrase that wraps, and one under emphasis, is found
The record is prose wrapped at a hundred columns; matched line by line, the first live search for a sentence of ADR 0025 found nothing. A line is matched together with the next, emphasis marks are ignored, and a hit still names the line it starts on.
This commit is contained in:
@@ -153,9 +153,15 @@ export class Records {
|
||||
* Where a phrase appears, case-insensitively, as written — no stemming, no ranking, because a
|
||||
* design record is found by its own words and a reader deciding which words matter would be a
|
||||
* second opinion about somebody else's document. Bounded, and says when it was.
|
||||
*
|
||||
* **The record is wrapped prose, and a phrase does not know where the line ends.** Every document
|
||||
* here wraps at a hundred columns, so a phrase of six words is as likely to straddle a line break
|
||||
* as not; matched line by line, the first live search for a sentence of ADR 0025 found nothing.
|
||||
* So a line is matched together with the one after it, joined by a space, and emphasis marks
|
||||
* are ignored — `**reachable**` is the word reachable. A hit still names the line it starts on.
|
||||
*/
|
||||
async search(query: string, limit = MOST_HITS): Promise<{ hits: Hit[]; more: boolean; commit: string }> {
|
||||
const needle = query.trim().toLowerCase();
|
||||
const needle = plain(query).toLowerCase().replace(/\s+/g, " ").trim();
|
||||
if (!needle) throw new Error("search for a phrase; an empty one matches every line of every document");
|
||||
const cap = Math.max(1, Math.min(limit, MOST_HITS));
|
||||
const hits: Hit[] = [];
|
||||
@@ -164,15 +170,22 @@ export class Records {
|
||||
const text = await fs.readFile(join(this.dir, path), "utf8");
|
||||
let heading = "";
|
||||
const lines = text.split("\n");
|
||||
const flat = lines.map((l) => plain(l).toLowerCase().replace(/\s+/g, " ").trim());
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i]!;
|
||||
if (/^#{1,6}\s/.test(line)) heading = line.replace(/^#+\s*/, "").trim();
|
||||
if (line.toLowerCase().includes(needle)) {
|
||||
const here = flat[i]!;
|
||||
const next = i + 1 < flat.length ? flat[i + 1]! : "";
|
||||
// On this line, or across the break into the next — but not a phrase that begins on the
|
||||
// next line alone, which is that line's hit.
|
||||
const onThis = here.includes(needle);
|
||||
const acrossTheBreak = !onThis && next !== "" && `${here} ${next}`.includes(needle) && !next.includes(needle);
|
||||
if (onThis || acrossTheBreak) {
|
||||
if (hits.length >= cap) {
|
||||
more = true;
|
||||
break;
|
||||
}
|
||||
hits.push({ path, line: i + 1, heading, text: line.trim() });
|
||||
hits.push({ path, line: i + 1, heading, text: acrossTheBreak ? `${line.trim()} ${lines[i + 1]!.trim()}` : line.trim() });
|
||||
}
|
||||
}
|
||||
if (more) break;
|
||||
@@ -249,6 +262,11 @@ export async function recordsFromEnv(env: NodeJS.ProcessEnv = process.env): Prom
|
||||
return new Records(dir, origin, repository);
|
||||
}
|
||||
|
||||
/** A line without its markdown emphasis, so a phrase matches the words and not the marks. */
|
||||
function plain(line: string): string {
|
||||
return line.replace(/[*_`]/g, "");
|
||||
}
|
||||
|
||||
async function exists(path: string): Promise<boolean> {
|
||||
try {
|
||||
await fs.stat(path);
|
||||
|
||||
Reference in New Issue
Block a user