~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/knotless

git that lives in your Atmosphere account

Forked from chadtmiller.com/knotless

0
0
88 lines · 3.1 KB Raw History
<script lang="ts">
	import type { Chunk, FileView, Range } from '#lib/lexicons/dev/knotless/searchCode.ts';
	import { repoHref, urlPath } from '#lib/format.ts';
	import Avatar from '#lib/atoms/Avatar.svelte';
	import { painted } from '#lib/highlight.ts';
	/**
	 * Files a code search found, each in a box: whose repository and its
	 * path over the lines around what was found, each line's number linking
	 * to that line of the file, at the commit that was searched, and what
	 * was found marked, and its highlighting painted.
	 */
	let { files }: { files: FileView[] } = $props();

	const encoder = new TextEncoder();
	const decoder = new TextDecoder();

	/** `line` in pieces, those `ranges` found marked: they count UTF-8 bytes. */
	function pieces(line: string, ranges: Range[]): { text: string; found: boolean }[] {
		const bytes = encoder.encode(line);
		const out: { text: string; found: boolean }[] = [];
		let at = 0;
		for (const r of [...ranges].sort((a, b) => a.byteStart - b.byteStart)) {
			const start = Math.max(at, r.byteStart);
			if (start > at) out.push({ text: decoder.decode(bytes.slice(at, start)), found: false });
			if (r.byteEnd > start)
				out.push({ text: decoder.decode(bytes.slice(start, r.byteEnd)), found: true });
			at = Math.max(at, r.byteEnd);
		}
		if (at < bytes.length) out.push({ text: decoder.decode(bytes.slice(at)), found: false });
		return out;
	}

	/** A chunk's lines, by number, in pieces. */
	const lines = (c: Chunk) =>
		c.content.split('\n').map((text, i) => {
			const n = c.startLine + i;
			return {
				n,
				pieces: pieces(
					text,
					c.ranges.filter((r) => r.line === n)
				)
			};
		});

	/** The file `f`, at the commit searched, at line `n` if given. */
	const href = (f: FileView, n?: number) =>
		repoHref(
			f.repo.owner?.handle ?? f.repo.did,
			f.repo.name,
			`/blob/${urlPath(f.commit ?? 'HEAD')}/${urlPath(f.path)}${n ? `#L${n}` : ''}`
		);
</script>

<div class="code-results">
	{#each files as f (f.repo.did + '/' + f.repo.name + '/' + f.path)}
		{@const owner = f.repo.owner?.handle ?? f.repo.did}
		<section class="box">
			<header class="boxhead">
				<Avatar name={owner} did={f.repo.did} src={f.repo.owner?.avatar} />
				<span class="code-result-name"
					><a class="mute" href={repoHref(owner, f.repo.name)}>{owner}/{f.repo.name}</a>
					<span class="faint">·</span>
					<a href={href(f)}
						><b
							>{#each f.path.split('/') as part, k (k)}{#if k}/<wbr />{/if}{part}{/each}</b
						></a
					></span
				>
				{#if f.language}<span class="code-result-lang faint">{f.language}</span>{/if}
			</header>
			{#if !f.chunks.length}<p class="code-result-path mute">Its path matches.</p>{/if}
			{#each f.chunks as c, i (i)}
				{@const shown = lines(c)}
				<div class="code code-result">
					<pre class="ln" aria-hidden="true">{#each shown as l (l.n)}<a
								href={href(f, l.n)}
								tabindex="-1">{l.n}</a
							>{'\n'}{/each}</pre>
					<pre class="src"><code use:painted={c.highlights}
							>{#each shown as l (l.n)}{#each l.pieces as p, j (j)}{#if p.found}<mark>{p.text}</mark
										>{:else}{p.text}{/if}{/each}{'\n'}{/each}</code
						></pre>
				</div>
			{/each}
		</section>
	{/each}
</div>