import { all, first } from "../db";
import { visibleReposClause } from "../repos";
import type { CodeSearchHit } from "~/lib/code-intel";
import type { Viewer } from "~/lib/types";
const MIN_TERM_LENGTH = 3;
const MAX_TERMS = 8;
const MAX_TERM_LENGTH = 100;
const RESULT_LIMIT = 30;
const SNIPPET_TOKENS = 24;
const PATH_PREFIX = "path:";
const SNIPPET = `snippet(code_fts, 1, char(1), char(2), '…', ${SNIPPET_TOKENS})`;
function quote(term: string): string {
return `"${term.replaceAll('"', '""')}"`;
}
/** Turns free text into a safe FTS5 expression: every term is quoted (trigram needs 3+ chars); `path:x` filters by path. */
export function ftsQuery(input: string): string | null {
const terms: string[] = [];
for (const raw of input.trim().split(/\s+/)) {
const isPath = raw.startsWith(PATH_PREFIX);
const term = (isPath ? raw.slice(PATH_PREFIX.length) : raw).slice(0, MAX_TERM_LENGTH);
if (term.length < MIN_TERM_LENGTH) continue;
terms.push(isPath ? `path : ${quote(term)}` : quote(term));
if (terms.length >= MAX_TERMS) break;
}
return terms.length > 0 ? terms.join(" ") : null;
}
const FROM = `FROM code_fts JOIN code_files f ON f.id = code_fts.rowid
JOIN repos r ON r.id = f.repo_id JOIN accounts a ON a.id = r.owner_id`;
export async function searchRepoCode(repoId: string, query: string): Promise<CodeSearchHit[]> {
return all<CodeSearchHit>(
`SELECT a.name AS owner, r.name AS repo, r.default_branch AS ref, f.path, ${SNIPPET} AS snippet ${FROM}
WHERE code_fts MATCH ?1 AND f.repo_id = ?2 ORDER BY rank LIMIT ${RESULT_LIMIT}`,
query,
repoId,