use codevier when showing search results

This commit is contained in:
Nikita Galaiko 2023-03-17 10:22:42 +01:00
parent d4d1a8e3fb
commit 396a932111
4 changed files with 24 additions and 215 deletions

View File

@ -1,47 +0,0 @@
<script lang="ts">
import { formatDistanceToNow } from 'date-fns';
import type { ProcessedSearchResult } from '.';
export let processedResult: ProcessedSearchResult;
</script>
<div class="flex flex-col">
<div class="mb-4">
<p class="mb-2 flex text-lg">
<span>{processedResult.searchResult.filePath}</span>
<span class="flex-grow" />
<span>{formatDistanceToNow(processedResult.timestamp)} ago</span>
</p>
<div
class="overflow-y-auto rounded-lg border border-zinc-700 bg-[#2F2F33] text-[#EBDBB2] drop-shadow-lg"
>
{#each processedResult.hunks as hunk, i}
{#if i > 0}
<div class="border-b border-[#52525B]" />
{/if}
<div class="flex flex-col px-6 py-3">
{#each hunk.lines as line}
{#if !line.hidden}
<div class="mb-px flex font-mono leading-4">
<span class="w-6 flex-shrink text-[#928374]"
>{line.lineNumber ? line.lineNumber : ''}</span
>
<pre
class="flex-grow rounded-sm
{line.operation === 'add'
? 'bg-[#14FF00]/20'
: line.operation === 'remove'
? 'bg-[#FF0000]/20'
: ''}
">{line.contentBeforeHit}<span class="rounded-sm bg-[#AC8F2F]">{line.contentAtHit}</span
>{line.contentAfterHit}</pre>
</div>
{:else}
<!-- <span>hidden</span> -->
{/if}
{/each}
</div>
{/each}
</div>
</div>
</div>

View File

@ -1,23 +0,0 @@
export { default as RenderedSearchResult } from './RenderedSearchResult.svelte';
import type { SearchResult } from '$lib/search';
export type ProcessedSearchResultLine = {
hidden: boolean;
contentBeforeHit: string;
contentAtHit: string;
contentAfterHit: string;
operation: string;
lineNumber: number | undefined;
hasKeyword: boolean;
};
export type ProcessedSearchRestultHunk = {
lines: ProcessedSearchResultLine[];
};
export type ProcessedSearchResult = {
searchResult: SearchResult;
hunks: ProcessedSearchRestultHunk[];
timestamp: Date;
};

View File

@ -1,135 +0,0 @@
import { listFiles } from '$lib/sessions';
import { list as listDeltas } from '$lib/deltas';
import type { SearchResult } from '$lib';
import { structuredPatch } from 'diff';
import type { Delta } from '$lib/deltas';
import { Operation } from '$lib/deltas';
import type { ProcessedSearchResult, ProcessedSearchRestultHunk } from '.';
export const processSearchResult = async (
searchResult: SearchResult,
query: string
): Promise<ProcessedSearchResult> => {
const [files, deltas] = await Promise.all([
listFiles({
projectId: searchResult.projectId,
sessionId: searchResult.sessionId,
paths: [searchResult.filePath]
}),
listDeltas({
projectId: searchResult.projectId,
sessionId: searchResult.sessionId,
paths: [searchResult.filePath]
})
]);
const hunks = getDiffHunksWithSearchTerm(
files[searchResult.filePath],
deltas[searchResult.filePath],
searchResult.index,
query
);
const processedHunks = [];
for (let i = 0; i < hunks.length; i++) {
const processedHunk: ProcessedSearchRestultHunk = {
lines: processHunkLines(hunks[i].lines, hunks[i].newStart, query)
};
processedHunks.push(processedHunk);
}
const processedSearchResult: ProcessedSearchResult = {
searchResult: searchResult,
hunks: processedHunks,
timestamp: new Date(deltas[searchResult.filePath][searchResult.index].timestampMs)
};
return processedSearchResult;
};
const applyDeltas = (text: string, deltas: Delta[]) => {
const operations = deltas.flatMap((delta) => delta.operations);
operations.forEach((operation) => {
if (Operation.isInsert(operation)) {
text =
text.slice(0, operation.insert[0]) + operation.insert[1] + text.slice(operation.insert[0]);
} else if (Operation.isDelete(operation)) {
text =
text.slice(0, operation.delete[0]) + text.slice(operation.delete[0] + operation.delete[1]);
}
});
return text;
};
const getDiffHunksWithSearchTerm = (
original: string,
deltas: Delta[],
idx: number,
query: string
) => {
if (!original) return [];
return structuredPatch(
'file',
'file',
applyDeltas(original, deltas.slice(0, idx)),
applyDeltas(original, [deltas[idx]]),
'header',
'header',
{ context: 1 }
).hunks.filter((hunk) => hunk.lines.some((l) => l.includes(query)));
};
const processHunkLines = (lines: string[], newStart: number, query: string) => {
const outLines = [];
let lineNumber = newStart;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
let contentBeforeHit = '';
let querySubstring = '';
let contentAfterHit = '';
if (!line.includes(query)) {
contentBeforeHit = line.slice(1);
} else {
const firstCharIndex = line.indexOf(query);
const lastCharIndex = firstCharIndex + query.length - 1;
contentBeforeHit = line.slice(1, firstCharIndex);
querySubstring = line.slice(firstCharIndex, lastCharIndex + 1);
contentAfterHit = line.slice(lastCharIndex + 1);
}
outLines.push({
hidden: false,
contentBeforeHit: contentBeforeHit,
contentAtHit: querySubstring,
contentAfterHit: contentAfterHit,
operation: line.startsWith('+') ? 'add' : line.startsWith('-') ? 'remove' : 'unmodified',
lineNumber: !line.startsWith('-') ? lineNumber : undefined,
hasKeyword: line.includes(query)
});
if (!line.startsWith('-')) {
lineNumber++;
}
}
const out = [];
for (let i = 0; i < outLines.length; i++) {
const prevLine = outLines[i - 1];
const nextLine = outLines[i + 1];
const line = outLines[i];
if (line.hasKeyword) {
out.push(line);
} else if (nextLine && nextLine.hasKeyword) {
// One line of context before the relevant line
out.push(line);
} else if (prevLine && prevLine.hasKeyword) {
// One line of context after the relevant line
out.push(line);
} else {
line.hidden = true;
out.push(line);
}
}
return out;
};

View File

@ -1,15 +1,17 @@
<script lang="ts">
import type { PageData } from './$types';
import { search } from '$lib';
import { RenderedSearchResult, type ProcessedSearchResult } from '$lib/components/search';
import { processSearchResult } from '$lib/components/search/process';
import { getContext } from 'svelte';
import type { Writable } from 'svelte/store';
import { listFiles } from '$lib/sessions';
import { list as listDeltas, type Delta } from '$lib/deltas';
import { CodeViewer } from '$lib/components';
import { formatDistanceToNow } from 'date-fns';
export let data: PageData;
const { project } = data;
let processedResults = [] as ProcessedSearchResult[];
let processedResults = [] as { doc: string; deltas: Delta[]; filepath: string }[];
let searchTerm: Writable<string> = getContext('searchTerm');
let stopProcessing = false;
@ -29,10 +31,13 @@
stopProcessing = false;
return;
}
const processedResult = await processSearchResult(r, query);
if (processedResult.hunks && processedResult.hunks.length > 0) {
processedResults = [...processedResults, processedResult];
}
console.log(r);
const { sessionId, projectId, filePath } = r;
const [doc, deltas] = await Promise.all([
listFiles({ projectId, sessionId, paths: [filePath] }).then((r) => r[filePath] ?? ''),
listDeltas({ projectId, sessionId, paths: [filePath] }).then((r) => r[filePath] ?? [])
]);
processedResults = [...processedResults, { doc, deltas, filepath: filePath }];
}
};
</script>
@ -51,9 +56,18 @@
{/if}
<ul class="flex flex-col gap-4">
{#each processedResults as r}
<li>
<RenderedSearchResult processedResult={r} />
{#each processedResults as { doc, deltas, filepath }}
{@const timestamp = deltas[deltas.length - 1].timestampMs}
<li class="flex flex-col gap-2">
<p class="flex justify-between text-lg">
<span>{filepath}</span>
<span>{formatDistanceToNow(timestamp)} ago</span>
</p>
<div
class="flex-auto overflow-auto rounded-lg border border-zinc-700 bg-[#2F2F33] text-[#EBDBB2] drop-shadow-lg"
>
<CodeViewer {doc} {deltas} {filepath} paddingLines={4} />
</div>
</li>
{/each}
</ul>