mirror of
https://github.com/sourcebot-dev/sourcebot.git
synced 2025-12-12 20:35:24 +00:00
fix: Improve symbol reference/definition list perf (#327)
This commit is contained in:
parent
81a9ea1e59
commit
91e803d7a6
3 changed files with 118 additions and 45 deletions
|
|
@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Fixed issue with the symbol hover popover clipping at the top of the page. [#326](https://github.com/sourcebot-dev/sourcebot/pull/326)
|
- Fixed issue with the symbol hover popover clipping at the top of the page. [#326](https://github.com/sourcebot-dev/sourcebot/pull/326)
|
||||||
|
- Fixed slow rendering issue with large reference/definition lists. [#327](https://github.com/sourcebot-dev/sourcebot/pull/327)
|
||||||
|
|
||||||
## [4.1.0] - 2025-06-02
|
## [4.1.0] - 2025-06-02
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,9 @@ interface LightweightCodeHighlighter {
|
||||||
renderWhitespace?: boolean;
|
renderWhitespace?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The maximum number of characters per line that we will display in the preview.
|
||||||
|
const MAX_NUMBER_OF_CHARACTER_PER_LINE = 1000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lightweight code highlighter that uses the Lezer parser to highlight code.
|
* Lightweight code highlighter that uses the Lezer parser to highlight code.
|
||||||
* This is helpful in scenarios where we need to highlight a ton of code snippets
|
* This is helpful in scenarios where we need to highlight a ton of code snippets
|
||||||
|
|
@ -49,12 +52,19 @@ export const LightweightCodeHighlighter = memo<LightweightCodeHighlighter>((prop
|
||||||
return code.trimEnd().split('\n');
|
return code.trimEnd().split('\n');
|
||||||
}, [code]);
|
}, [code]);
|
||||||
|
|
||||||
|
const isFileTooLargeToDisplay = useMemo(() => {
|
||||||
|
return unhighlightedLines.some(line => line.length > MAX_NUMBER_OF_CHARACTER_PER_LINE);
|
||||||
|
}, [code]);
|
||||||
|
|
||||||
const [highlightedLines, setHighlightedLines] = useState<React.ReactNode[] | null>(null);
|
const [highlightedLines, setHighlightedLines] = useState<React.ReactNode[] | null>(null);
|
||||||
|
|
||||||
const highlightStyle = useCodeMirrorHighlighter();
|
const highlightStyle = useCodeMirrorHighlighter();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (isFileTooLargeToDisplay) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
measure(() => Promise.all(
|
measure(() => Promise.all(
|
||||||
unhighlightedLines
|
unhighlightedLines
|
||||||
.map(async (line, index) => {
|
.map(async (line, index) => {
|
||||||
|
|
@ -103,12 +113,21 @@ export const LightweightCodeHighlighter = memo<LightweightCodeHighlighter>((prop
|
||||||
const lineNumberDigits = String(lineCount).length;
|
const lineNumberDigits = String(lineCount).length;
|
||||||
const lineNumberWidth = `${lineNumberDigits + 2}ch`; // +2 for padding
|
const lineNumberWidth = `${lineNumberDigits + 2}ch`; // +2 for padding
|
||||||
|
|
||||||
|
if (isFileTooLargeToDisplay) {
|
||||||
|
return (
|
||||||
|
<div className="font-mono text-sm px-2">
|
||||||
|
File too large to display in preview.
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
fontFamily: tailwind.theme.fontFamily.editor,
|
fontFamily: tailwind.theme.fontFamily.editor,
|
||||||
fontSize: tailwind.theme.fontSize.editor,
|
fontSize: tailwind.theme.fontSize.editor,
|
||||||
whiteSpace: renderWhitespace ? 'pre-wrap' : 'none',
|
whiteSpace: renderWhitespace ? 'pre-wrap' : 'none',
|
||||||
|
wordBreak: 'break-all',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{(highlightedLines ?? unhighlightedLines).map((line, index) => (
|
{(highlightedLines ?? unhighlightedLines).map((line, index) => (
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,21 @@
|
||||||
import { useBrowseNavigation } from "@/app/[domain]/browse/hooks/useBrowseNavigation";
|
import { useBrowseNavigation } from "@/app/[domain]/browse/hooks/useBrowseNavigation";
|
||||||
import { FileHeader } from "@/app/[domain]/components/fileHeader";
|
import { FileHeader } from "@/app/[domain]/components/fileHeader";
|
||||||
import { LightweightCodeHighlighter } from "@/app/[domain]/components/lightweightCodeHighlighter";
|
import { LightweightCodeHighlighter } from "@/app/[domain]/components/lightweightCodeHighlighter";
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
||||||
import { FindRelatedSymbolsResponse } from "@/features/codeNav/types";
|
import { FindRelatedSymbolsResponse } from "@/features/codeNav/types";
|
||||||
import { RepositoryInfo, SourceRange } from "@/features/search/types";
|
import { RepositoryInfo, SourceRange } from "@/features/search/types";
|
||||||
import { base64Decode } from "@/lib/utils";
|
import { base64Decode } from "@/lib/utils";
|
||||||
import { useMemo } from "react";
|
import { useMemo, useRef } from "react";
|
||||||
import useCaptureEvent from "@/hooks/useCaptureEvent";
|
import useCaptureEvent from "@/hooks/useCaptureEvent";
|
||||||
|
import { useVirtualizer } from "@tanstack/react-virtual";
|
||||||
|
|
||||||
interface ReferenceListProps {
|
interface ReferenceListProps {
|
||||||
data: FindRelatedSymbolsResponse;
|
data: FindRelatedSymbolsResponse;
|
||||||
revisionName: string;
|
revisionName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ESTIMATED_LINE_HEIGHT_PX = 30;
|
||||||
|
const ESTIMATED_MATCH_CONTAINER_HEIGHT_PX = 30;
|
||||||
|
|
||||||
export const ReferenceList = ({
|
export const ReferenceList = ({
|
||||||
data,
|
data,
|
||||||
revisionName,
|
revisionName,
|
||||||
|
|
@ -29,14 +32,63 @@ export const ReferenceList = ({
|
||||||
const { navigateToPath } = useBrowseNavigation();
|
const { navigateToPath } = useBrowseNavigation();
|
||||||
const captureEvent = useCaptureEvent();
|
const captureEvent = useCaptureEvent();
|
||||||
|
|
||||||
return (
|
// Virtualization setup
|
||||||
<ScrollArea className="h-full">
|
const parentRef = useRef<HTMLDivElement>(null);
|
||||||
{data.files.map((file, index) => {
|
const virtualizer = useVirtualizer({
|
||||||
const repoInfo = repoInfoMap[file.repositoryId];
|
count: data.files.length,
|
||||||
|
getScrollElement: () => parentRef.current,
|
||||||
|
estimateSize: (index) => {
|
||||||
|
const file = data.files[index];
|
||||||
|
|
||||||
|
const estimatedSize =
|
||||||
|
file.matches.length * ESTIMATED_LINE_HEIGHT_PX +
|
||||||
|
ESTIMATED_MATCH_CONTAINER_HEIGHT_PX;
|
||||||
|
|
||||||
|
return estimatedSize;
|
||||||
|
},
|
||||||
|
overscan: 5,
|
||||||
|
enabled: true,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={index}>
|
<div
|
||||||
<div className="bg-accent py-1 px-2 flex flex-row sticky top-0">
|
ref={parentRef}
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
overflowY: "auto",
|
||||||
|
contain: "strict",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: virtualizer.getTotalSize(),
|
||||||
|
width: "100%",
|
||||||
|
position: "relative",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{virtualizer.getVirtualItems().map((virtualRow) => {
|
||||||
|
const file = data.files[virtualRow.index];
|
||||||
|
const repoInfo = repoInfoMap[file.repositoryId];
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={virtualRow.key}
|
||||||
|
data-index={virtualRow.index}
|
||||||
|
ref={virtualizer.measureElement}
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
transform: `translateY(${virtualRow.start}px)`,
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="bg-accent py-1 px-2 flex flex-row sticky top-0 z-10"
|
||||||
|
style={{
|
||||||
|
top: `-${virtualRow.start}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<FileHeader
|
<FileHeader
|
||||||
repo={{
|
repo={{
|
||||||
name: repoInfo.name,
|
name: repoInfo.name,
|
||||||
|
|
@ -71,10 +123,11 @@ export const ReferenceList = ({
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
})}
|
})}
|
||||||
</ScrollArea>
|
</div>
|
||||||
)
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue