Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/app/api/(client)/client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { FileSourceResponse, fileSourceResponseSchema, ListRepositoriesResponse, listRepositoriesResponseSchema, SearchRequest, SearchResponse, searchResponseSchema } from "@/lib/schemas";
import { fileSourceResponseSchema, listRepositoriesResponseSchema, searchResponseSchema } from "@/lib/schemas";
import { FileSourceResponse, ListRepositoriesResponse, SearchRequest, SearchResponse } from "@/lib/types";

export const search = async (body: SearchRequest): Promise<SearchResponse> => {
const result = await fetch(`/api/search`, {
Expand Down
19 changes: 19 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,23 @@
body {
@apply bg-background text-foreground;
}
}

.cm-editor .cm-gutters {
background-color: transparent;
border-right: none;
}

.cm-editor .cm-lineNumbers .cm-gutterElement {
padding-left: 0.5;
text-align: left;
}

.cm-editor .cm-searchMatch {
border: dotted;
background: transparent;
}

.cm-editor .cm-searchMatch-selected {
border: solid;
}
2 changes: 1 addition & 1 deletion src/app/repositoryCarousel.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
'use client';

import { Repository } from "@/lib/schemas";
import {
Carousel,
CarouselContent,
Expand All @@ -11,6 +10,7 @@ import { getRepoCodeHostInfo } from "@/lib/utils";
import Image from "next/image";
import { FileIcon } from "@radix-ui/react-icons";
import clsx from "clsx";
import { Repository } from "@/lib/types";

interface RepositoryCarouselProps {
repos: Repository[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useSyntaxHighlightingExtension } from "@/hooks/useSyntaxHighlightingExt
import { useThemeNormalized } from "@/hooks/useThemeNormalized";
import { gutterWidthExtension } from "@/lib/extensions/gutterWidthExtension";
import { highlightRanges, searchResultHighlightExtension } from "@/lib/extensions/searchResultHighlightExtension";
import { SearchResultFileMatch } from "@/lib/schemas";
import { SearchResultFileMatch } from "@/lib/types";
import { defaultKeymap } from "@codemirror/commands";
import { search } from "@codemirror/search";
import { EditorView, keymap } from "@codemirror/view";
Expand All @@ -28,19 +28,19 @@ export interface CodePreviewFile {
language: string;
}

interface CodePreviewPanelProps {
interface CodePreviewProps {
file?: CodePreviewFile;
selectedMatchIndex: number;
onSelectedMatchIndexChange: (index: number) => void;
onClose: () => void;
}

export const CodePreviewPanel = ({
export const CodePreview = ({
file,
selectedMatchIndex,
onSelectedMatchIndexChange,
onClose,
}: CodePreviewPanelProps) => {
}: CodePreviewProps) => {
const editorRef = useRef<ReactCodeMirrorRef>(null);

const [ keymapType ] = useKeymapType();
Expand All @@ -67,6 +67,7 @@ export const CodePreviewPanel = ({
keymapExtension,
gutterWidthExtension,
syntaxHighlighting,
EditorView.lineWrapping,
searchResultHighlightExtension(),
search({
top: true,
Expand Down
63 changes: 63 additions & 0 deletions src/app/search/components/codePreviewPanel/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
'use client';

import { fetchFileSource } from "@/app/api/(client)/client";
import { getCodeHostFilePreviewLink } from "@/lib/utils";
import { useQuery } from "@tanstack/react-query";
import { CodePreview, CodePreviewFile } from "./codePreview";
import { SearchResultFile } from "@/lib/types";

interface CodePreviewPanelProps {
fileMatch?: SearchResultFile;
onClose: () => void;
selectedMatchIndex: number;
onSelectedMatchIndexChange: (index: number) => void;
}

export const CodePreviewPanel = ({
fileMatch,
onClose,
selectedMatchIndex,
onSelectedMatchIndexChange,
}: CodePreviewPanelProps) => {

const { data: file } = useQuery({
queryKey: ["source", fileMatch?.FileName, fileMatch?.Repository],
queryFn: async (): Promise<CodePreviewFile | undefined> => {
if (!fileMatch) {
return undefined;
}

return fetchFileSource(fileMatch.FileName, fileMatch.Repository)
.then(({ source }) => {
// @todo : refector this to use the templates provided by zoekt.
const link = getCodeHostFilePreviewLink(fileMatch.Repository, fileMatch.FileName)

const decodedSource = atob(source);

// Filter out filename matches
const filteredMatches = fileMatch.ChunkMatches.filter((match) => {
return !match.FileName;
});

return {
content: decodedSource,
filepath: fileMatch.FileName,
matches: filteredMatches,
link: link,
language: fileMatch.Language,
};
});
},
enabled: fileMatch !== undefined,
});

return (
<CodePreview
file={file}
onClose={onClose}
selectedMatchIndex={selectedMatchIndex}
onSelectedMatchIndexChange={onSelectedMatchIndexChange}
/>
)

}
125 changes: 125 additions & 0 deletions src/app/search/components/searchResultsPanel/codePreview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
'use client';

import { useExtensionWithDependency } from "@/hooks/useExtensionWithDependency";
import { useSyntaxHighlightingExtension } from "@/hooks/useSyntaxHighlightingExtension";
import { useThemeNormalized } from "@/hooks/useThemeNormalized";
import { lineOffsetExtension } from "@/lib/extensions/lineOffsetExtension";
import { SearchResultRange } from "@/lib/types";
import CodeMirror, { Decoration, DecorationSet, EditorState, EditorView, ReactCodeMirrorRef, StateField, Transaction } from "@uiw/react-codemirror";
import { useMemo, useRef } from "react";

const markDecoration = Decoration.mark({
class: "cm-searchMatch-selected"
});

interface CodePreviewProps {
content: string,
language: string,
ranges: SearchResultRange[],
lineOffset: number,
}

export const CodePreview = ({
content,
language,
ranges,
lineOffset,
}: CodePreviewProps) => {
const editorRef = useRef<ReactCodeMirrorRef>(null);
const { theme } = useThemeNormalized();

const syntaxHighlighting = useSyntaxHighlightingExtension(language, editorRef.current?.view);

const rangeHighlighting = useExtensionWithDependency(editorRef.current?.view ?? null, () => {
return [
StateField.define<DecorationSet>({
create(editorState: EditorState) {
const document = editorState.doc;

const decorations = ranges
.sort((a, b) => {
return a.Start.ByteOffset - b.Start.ByteOffset;
})
.filter(({ Start, End }) => {
const startLine = Start.LineNumber - lineOffset;
const endLine = End.LineNumber - lineOffset;

if (
startLine < 1 ||
endLine < 1 ||
startLine > document.lines ||
endLine > document.lines
) {
return false;
}
return true;
})
.map(({ Start, End }) => {
const startLine = Start.LineNumber - lineOffset;
const endLine = End.LineNumber - lineOffset;

const from = document.line(startLine).from + Start.Column - 1;
const to = document.line(endLine).from + End.Column - 1;
return markDecoration.range(from, to);
});

return Decoration.set(decorations);
},
update(highlights: DecorationSet, _transaction: Transaction) {
return highlights;
},
provide: (field) => EditorView.decorations.from(field),
}),
];
}, [ranges, lineOffset]);

const extensions = useMemo(() => {
return [
syntaxHighlighting,
EditorView.lineWrapping,
lineOffsetExtension(lineOffset),
rangeHighlighting,
];
}, [syntaxHighlighting, lineOffset, rangeHighlighting]);

return (
<CodeMirror
ref={editorRef}
readOnly={true}
editable={false}
value={content}
theme={theme === "dark" ? "dark" : "light"}
basicSetup={{
lineNumbers: true,
syntaxHighlighting: true,

// Disable all this other stuff...
... {
foldGutter: false,
highlightActiveLineGutter: false,
highlightSpecialChars: false,
history: false,
drawSelection: false,
dropCursor: false,
allowMultipleSelections: false,
indentOnInput: false,
bracketMatching: false,
closeBrackets: false,
autocompletion: false,
rectangularSelection: false,
crosshairCursor: false,
highlightActiveLine: false,
highlightSelectionMatches: false,
closeBracketsKeymap: false,
defaultKeymap: false,
searchKeymap: false,
historyKeymap: false,
foldKeymap: false,
completionKeymap: false,
lintKeymap: false,
}
}}
extensions={extensions}
/>
)
}
48 changes: 48 additions & 0 deletions src/app/search/components/searchResultsPanel/fileMatch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
'use client';

import { useMemo } from "react";
import { CodePreview } from "./codePreview";
import { SearchResultFile, SearchResultFileMatch } from "@/lib/types";


interface FileMatchProps {
match: SearchResultFileMatch;
file: SearchResultFile;
onOpen: () => void;
}

export const FileMatch = ({
match,
file,
onOpen,
}: FileMatchProps) => {
const content = useMemo(() => {
return atob(match.Content);
}, [match.Content]);

// If it's just the title, don't show a code preview
if (match.FileName) {
return null;
}

return (
<div
tabIndex={0}
className="cursor-pointer p-1 focus:ring-inset focus:ring-4 bg-white dark:bg-[#282c34]"
onKeyDown={(e) => {
if (e.key !== "Enter") {
return;
}
onOpen();
}}
onClick={onOpen}
>
<CodePreview
content={content}
language={file.Language}
ranges={match.Ranges}
lineOffset={match.ContentStart.LineNumber - 1}
/>
</div>
);
}
Loading