mirror of
https://github.com/sourcebot-dev/sourcebot.git
synced 2025-12-13 04:45:19 +00:00
Collapse search results when there is too many. Also sort results by line number
This commit is contained in:
parent
687ebe9cda
commit
9c7db7509f
3 changed files with 60 additions and 22 deletions
|
|
@ -102,9 +102,9 @@ export default function SearchPage() {
|
||||||
<ResizablePanel minSize={20}>
|
<ResizablePanel minSize={20}>
|
||||||
<SearchResults
|
<SearchResults
|
||||||
fileMatches={fileMatches}
|
fileMatches={fileMatches}
|
||||||
onOpenFileMatch={(match) => {
|
onOpenFileMatch={(fileMatch, matchIndex) => {
|
||||||
setSelectedFile(match);
|
setSelectedFile(fileMatch);
|
||||||
setSelectedMatchIndex(0);
|
setSelectedMatchIndex(matchIndex);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</ResizablePanel>
|
</ResizablePanel>
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,14 @@ import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { ZoektFileMatch } from "@/lib/types";
|
import { ZoektFileMatch } from "@/lib/types";
|
||||||
import { Scrollbar } from "@radix-ui/react-scroll-area";
|
import { Scrollbar } from "@radix-ui/react-scroll-area";
|
||||||
|
import { useMemo, useState } from "react";
|
||||||
|
import { DoubleArrowDownIcon, DoubleArrowUpIcon } from "@radix-ui/react-icons";
|
||||||
|
|
||||||
|
const MAX_MATCHES_TO_PREVIEW = 5;
|
||||||
|
|
||||||
interface SearchResultsProps {
|
interface SearchResultsProps {
|
||||||
fileMatches: ZoektFileMatch[];
|
fileMatches: ZoektFileMatch[];
|
||||||
onOpenFileMatch: (match: ZoektFileMatch) => void;
|
onOpenFileMatch: (fileMatch: ZoektFileMatch, matchIndex: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SearchResults = ({
|
export const SearchResults = ({
|
||||||
|
|
@ -17,12 +21,12 @@ export const SearchResults = ({
|
||||||
return (
|
return (
|
||||||
<ScrollArea className="h-full">
|
<ScrollArea className="h-full">
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
{fileMatches.map((match, index) => (
|
{fileMatches.map((fileMatch, index) => (
|
||||||
<FileMatch
|
<FileMatch
|
||||||
key={index}
|
key={index}
|
||||||
match={match}
|
match={fileMatch}
|
||||||
onOpenFile={() => {
|
onOpenFile={(matchIndex) => {
|
||||||
onOpenFileMatch(match);
|
onOpenFileMatch(fileMatch, matchIndex);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
@ -34,7 +38,7 @@ export const SearchResults = ({
|
||||||
|
|
||||||
interface FileMatchProps {
|
interface FileMatchProps {
|
||||||
match: ZoektFileMatch;
|
match: ZoektFileMatch;
|
||||||
onOpenFile: () => void;
|
onOpenFile: (matchIndex: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FileMatch = ({
|
const FileMatch = ({
|
||||||
|
|
@ -42,12 +46,29 @@ const FileMatch = ({
|
||||||
onOpenFile,
|
onOpenFile,
|
||||||
}: FileMatchProps) => {
|
}: FileMatchProps) => {
|
||||||
|
|
||||||
|
const [showAll, setShowAll] = useState(false);
|
||||||
|
const matchCount = useMemo(() => {
|
||||||
|
return match.Matches.length;
|
||||||
|
}, [match]);
|
||||||
|
|
||||||
|
const matches = useMemo(() => {
|
||||||
|
const sortedMatches = match.Matches.sort((a, b) => {
|
||||||
|
return a.LineNum - b.LineNum;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!showAll) {
|
||||||
|
return sortedMatches.slice(0, MAX_MATCHES_TO_PREVIEW);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sortedMatches;
|
||||||
|
}, [match, showAll]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="bg-cyan-200 dark:bg-cyan-900 primary-foreground px-2">
|
<div className="bg-cyan-200 dark:bg-cyan-900 primary-foreground px-2">
|
||||||
<span>{match.Repo} · {match.FileName}</span>
|
<span>{match.Repo} · {match.FileName}</span>
|
||||||
</div>
|
</div>
|
||||||
{match.Matches.map((match, index) => {
|
{matches.map((match, index) => {
|
||||||
const fragment = match.Fragments[0];
|
const fragment = match.Fragments[0];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -55,14 +76,25 @@ const FileMatch = ({
|
||||||
key={index}
|
key={index}
|
||||||
className="font-mono px-4 py-0.5 text-sm cursor-pointer"
|
className="font-mono px-4 py-0.5 text-sm cursor-pointer"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onOpenFile();
|
onOpenFile(index);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<p>{match.LineNum}: {fragment.Pre}<span className="font-bold">{fragment.Match}</span>{fragment.Post}</p>
|
<p>{match.LineNum > 0 ? match.LineNum : "file match"}: {fragment.Pre}<span className="font-bold">{fragment.Match}</span>{fragment.Post}</p>
|
||||||
<Separator />
|
<Separator />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
{matchCount > MAX_MATCHES_TO_PREVIEW && (
|
||||||
|
<div className="px-4">
|
||||||
|
<p
|
||||||
|
onClick={() => setShowAll(!showAll)}
|
||||||
|
className="text-blue-500 cursor-pointer text-sm flex flex-row items-center gap-2"
|
||||||
|
>
|
||||||
|
{showAll ? <DoubleArrowUpIcon className="w-3 h-3" /> : <DoubleArrowDownIcon className="w-3 h-3" />}
|
||||||
|
{showAll ? `Show fewer matching lines` : `Show ${matchCount - MAX_MATCHES_TO_PREVIEW} more matching lines`}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,11 +34,13 @@ const matchHighlighter = StateField.define<DecorationSet>({
|
||||||
if (effect.is(setMatchState)) {
|
if (effect.is(setMatchState)) {
|
||||||
const { matches, selectedMatchIndex } = effect.value;
|
const { matches, selectedMatchIndex } = effect.value;
|
||||||
|
|
||||||
const decorations = matches.map((match, index) => {
|
const decorations = matches
|
||||||
const { from, to } = getMatchRange(match, transaction.newDoc);
|
.filter((match) => match.LineNum > 0)
|
||||||
const mark = index === selectedMatchIndex ? selectedMatchMark : matchMark;
|
.map((match, index) => {
|
||||||
return mark.range(from, to);
|
const { from, to } = getMatchRange(match, transaction.newDoc);
|
||||||
});
|
const mark = index === selectedMatchIndex ? selectedMatchMark : matchMark;
|
||||||
|
return mark.range(from, to);
|
||||||
|
});
|
||||||
|
|
||||||
highlights = Decoration.set(decorations)
|
highlights = Decoration.set(decorations)
|
||||||
}
|
}
|
||||||
|
|
@ -77,11 +79,15 @@ export const markMatches = (selectedMatchIndex: number, matches: ZoektMatch[], v
|
||||||
|
|
||||||
if (selectedMatchIndex >= 0 && selectedMatchIndex < matches.length) {
|
if (selectedMatchIndex >= 0 && selectedMatchIndex < matches.length) {
|
||||||
const match = matches[selectedMatchIndex];
|
const match = matches[selectedMatchIndex];
|
||||||
const { from, to } = getMatchRange(match, view.state.doc);
|
|
||||||
const selection = EditorSelection.range(from, to);
|
// Don't scroll if the match is on the filename.
|
||||||
effects.push(EditorView.scrollIntoView(selection, {
|
if (match.LineNum > 0) {
|
||||||
y: "start",
|
const { from, to } = getMatchRange(match, view.state.doc);
|
||||||
}));
|
const selection = EditorSelection.range(from, to);
|
||||||
|
effects.push(EditorView.scrollIntoView(selection, {
|
||||||
|
y: "start",
|
||||||
|
}));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
view.dispatch({ effects });
|
view.dispatch({ effects });
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue