74 lines · 2.7 KB
import type { CodeSearchLine } from "./code-intel";

export const CODE_PAGE_SIZE = 20;
/** Deep pages re-rank every match, so paging stops here; narrow the query to see further. */
export const CODE_MAX_PAGES = 50;

const MAX_LINE_CHARS = 240;
const LEAD_CHARS = 60;

/** Case-insensitive [start, end) spans of every term in `text`, merged where they overlap. */
export function matchRanges(text: string, terms: string[]): Array<[number, number]> {
  const lower = text.toLowerCase();
  const spans: Array<[number, number]> = [];
  for (const term of terms) {
    const needle = term.toLowerCase();
    if (!needle) continue;
    for (let at = lower.indexOf(needle); at !== -1; at = lower.indexOf(needle, at + needle.length)) {
      spans.push([at, at + needle.length]);
    }
  }
  spans.sort((left, right) => left[0] - right[0]);
  const merged: Array<[number, number]> = [];
  for (const span of spans) {
    const last = merged.at(-1);
    if (last && span[0] <= last[1]) last[1] = Math.max(last[1], span[1]);
    else merged.push([span[0], span[1]]);
  }
  return merged;
}

/** Long lines are cut to a window that starts a little before the first match. */
function clip(text: string, ranges: Array<[number, number]>): { text: string; offset: number } {
  if (text.length <= MAX_LINE_CHARS) return { text, offset: 0 };
  const offset = Math.max(0, Math.min((ranges[0]?.[0] ?? 0) - LEAD_CHARS, text.length - MAX_LINE_CHARS));
  return { text: text.slice(offset, offset + MAX_LINE_CHARS), offset };
}

/**
 * The first `limit` lines of `content` containing any term, with 1-based line numbers and highlight
 * spans, plus how many lines matched in total.