mirror of
https://github.com/anomalyco/opencode.git
synced 2026-04-21 05:14:50 +00:00
Compare commits
1 Commits
facade/con
...
no-diff-vi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb29742b57 |
@@ -1,4 +1,4 @@
|
|||||||
import { DIFFS_TAG_NAME, FileDiff, VirtualizedFileDiff } from "@pierre/diffs"
|
import { DIFFS_TAG_NAME, FileDiff } from "@pierre/diffs"
|
||||||
import { type PreloadFileDiffResult, type PreloadMultiFileDiffResult } from "@pierre/diffs/ssr"
|
import { type PreloadFileDiffResult, type PreloadMultiFileDiffResult } from "@pierre/diffs/ssr"
|
||||||
import { createEffect, onCleanup, onMount, Show, splitProps } from "solid-js"
|
import { createEffect, onCleanup, onMount, Show, splitProps } from "solid-js"
|
||||||
import { Dynamic, isServer } from "solid-js/web"
|
import { Dynamic, isServer } from "solid-js/web"
|
||||||
@@ -13,7 +13,6 @@ import {
|
|||||||
notifyShadowReady,
|
notifyShadowReady,
|
||||||
observeViewerScheme,
|
observeViewerScheme,
|
||||||
} from "../pierre/file-runtime"
|
} from "../pierre/file-runtime"
|
||||||
import { acquireVirtualizer, virtualMetrics } from "../pierre/virtualizer"
|
|
||||||
import { File, type DiffFileProps, type FileProps } from "./file"
|
import { File, type DiffFileProps, type FileProps } from "./file"
|
||||||
|
|
||||||
type DiffPreload<T> = PreloadMultiFileDiffResult<T> | PreloadFileDiffResult<T>
|
type DiffPreload<T> = PreloadMultiFileDiffResult<T> | PreloadFileDiffResult<T>
|
||||||
@@ -26,7 +25,6 @@ function DiffSSRViewer<T>(props: SSRDiffFileProps<T>) {
|
|||||||
let container!: HTMLDivElement
|
let container!: HTMLDivElement
|
||||||
let fileDiffRef!: HTMLElement
|
let fileDiffRef!: HTMLElement
|
||||||
let fileDiffInstance: FileDiff<T> | undefined
|
let fileDiffInstance: FileDiff<T> | undefined
|
||||||
let sharedVirtualizer: NonNullable<ReturnType<typeof acquireVirtualizer>> | undefined
|
|
||||||
|
|
||||||
const ready = createReadyWatcher()
|
const ready = createReadyWatcher()
|
||||||
const workerPool = useWorkerPool(props.diffStyle)
|
const workerPool = useWorkerPool(props.diffStyle)
|
||||||
@@ -51,14 +49,6 @@ function DiffSSRViewer<T>(props: SSRDiffFileProps<T>) {
|
|||||||
|
|
||||||
const getRoot = () => fileDiffRef?.shadowRoot ?? undefined
|
const getRoot = () => fileDiffRef?.shadowRoot ?? undefined
|
||||||
|
|
||||||
const getVirtualizer = () => {
|
|
||||||
if (sharedVirtualizer) return sharedVirtualizer.virtualizer
|
|
||||||
const result = acquireVirtualizer(container)
|
|
||||||
if (!result) return
|
|
||||||
sharedVirtualizer = result
|
|
||||||
return result.virtualizer
|
|
||||||
}
|
|
||||||
|
|
||||||
const setSelectedLines = (range: DiffFileProps<T>["selectedLines"], attempt = 0) => {
|
const setSelectedLines = (range: DiffFileProps<T>["selectedLines"], attempt = 0) => {
|
||||||
const diff = fileDiffInstance
|
const diff = fileDiffInstance
|
||||||
if (!diff) return
|
if (!diff) return
|
||||||
@@ -92,27 +82,15 @@ function DiffSSRViewer<T>(props: SSRDiffFileProps<T>) {
|
|||||||
|
|
||||||
onCleanup(observeViewerScheme(() => fileDiffRef))
|
onCleanup(observeViewerScheme(() => fileDiffRef))
|
||||||
|
|
||||||
const virtualizer = getVirtualizer()
|
|
||||||
const annotations = local.annotations ?? local.preloadedDiff.annotations ?? []
|
const annotations = local.annotations ?? local.preloadedDiff.annotations ?? []
|
||||||
fileDiffInstance = virtualizer
|
fileDiffInstance = new FileDiff<T>(
|
||||||
? new VirtualizedFileDiff<T>(
|
{
|
||||||
{
|
...createDefaultOptions(props.diffStyle),
|
||||||
...createDefaultOptions(props.diffStyle),
|
...others,
|
||||||
...others,
|
...(local.preloadedDiff.options ?? {}),
|
||||||
...(local.preloadedDiff.options ?? {}),
|
},
|
||||||
},
|
workerPool,
|
||||||
virtualizer,
|
)
|
||||||
virtualMetrics,
|
|
||||||
workerPool,
|
|
||||||
)
|
|
||||||
: new FileDiff<T>(
|
|
||||||
{
|
|
||||||
...createDefaultOptions(props.diffStyle),
|
|
||||||
...others,
|
|
||||||
...(local.preloadedDiff.options ?? {}),
|
|
||||||
},
|
|
||||||
workerPool,
|
|
||||||
)
|
|
||||||
|
|
||||||
applyViewerScheme(fileDiffRef)
|
applyViewerScheme(fileDiffRef)
|
||||||
|
|
||||||
@@ -163,8 +141,6 @@ function DiffSSRViewer<T>(props: SSRDiffFileProps<T>) {
|
|||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
clearReadyWatcher(ready)
|
clearReadyWatcher(ready)
|
||||||
fileDiffInstance?.cleanUp()
|
fileDiffInstance?.cleanUp()
|
||||||
sharedVirtualizer?.release()
|
|
||||||
sharedVirtualizer = undefined
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { sampledChecksum } from "@opencode-ai/util/encode"
|
import { sampledChecksum } from "@opencode-ai/util/encode"
|
||||||
import {
|
import {
|
||||||
DEFAULT_VIRTUAL_FILE_METRICS,
|
|
||||||
type DiffLineAnnotation,
|
type DiffLineAnnotation,
|
||||||
type FileContents,
|
type FileContents,
|
||||||
type FileDiffMetadata,
|
type FileDiffMetadata,
|
||||||
@@ -10,10 +9,6 @@ import {
|
|||||||
type FileOptions,
|
type FileOptions,
|
||||||
type LineAnnotation,
|
type LineAnnotation,
|
||||||
type SelectedLineRange,
|
type SelectedLineRange,
|
||||||
type VirtualFileMetrics,
|
|
||||||
VirtualizedFile,
|
|
||||||
VirtualizedFileDiff,
|
|
||||||
Virtualizer,
|
|
||||||
} from "@pierre/diffs"
|
} from "@pierre/diffs"
|
||||||
import { type PreloadFileDiffResult, type PreloadMultiFileDiffResult } from "@pierre/diffs/ssr"
|
import { type PreloadFileDiffResult, type PreloadMultiFileDiffResult } from "@pierre/diffs/ssr"
|
||||||
import { createMediaQuery } from "@solid-primitives/media"
|
import { createMediaQuery } from "@solid-primitives/media"
|
||||||
@@ -40,19 +35,10 @@ import {
|
|||||||
readShadowLineSelection,
|
readShadowLineSelection,
|
||||||
} from "../pierre/file-selection"
|
} from "../pierre/file-selection"
|
||||||
import { createLineNumberSelectionBridge, restoreShadowTextSelection } from "../pierre/selection-bridge"
|
import { createLineNumberSelectionBridge, restoreShadowTextSelection } from "../pierre/selection-bridge"
|
||||||
import { acquireVirtualizer, virtualMetrics } from "../pierre/virtualizer"
|
|
||||||
import { getWorkerPool } from "../pierre/worker"
|
import { getWorkerPool } from "../pierre/worker"
|
||||||
import { FileMedia, type FileMediaOptions } from "./file-media"
|
import { FileMedia, type FileMediaOptions } from "./file-media"
|
||||||
import { FileSearchBar } from "./file-search"
|
import { FileSearchBar } from "./file-search"
|
||||||
|
|
||||||
const VIRTUALIZE_BYTES = 500_000
|
|
||||||
|
|
||||||
const codeMetrics = {
|
|
||||||
...DEFAULT_VIRTUAL_FILE_METRICS,
|
|
||||||
lineHeight: 24,
|
|
||||||
fileGap: 0,
|
|
||||||
} satisfies Partial<VirtualFileMetrics>
|
|
||||||
|
|
||||||
type SharedProps<T> = {
|
type SharedProps<T> = {
|
||||||
annotations?: LineAnnotation<T>[] | DiffLineAnnotation<T>[]
|
annotations?: LineAnnotation<T>[] | DiffLineAnnotation<T>[]
|
||||||
selectedLines?: SelectedLineRange | null
|
selectedLines?: SelectedLineRange | null
|
||||||
@@ -386,11 +372,6 @@ type AnnotationTarget<A> = {
|
|||||||
rerender: () => void
|
rerender: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
type VirtualStrategy = {
|
|
||||||
get: () => Virtualizer | undefined
|
|
||||||
cleanup: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
function useModeViewer(config: ModeConfig, adapter: ModeAdapter) {
|
function useModeViewer(config: ModeConfig, adapter: ModeAdapter) {
|
||||||
return useFileViewer({
|
return useFileViewer({
|
||||||
enableLineSelection: config.enableLineSelection,
|
enableLineSelection: config.enableLineSelection,
|
||||||
@@ -532,64 +513,6 @@ function scrollParent(el: HTMLElement): HTMLElement | undefined {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createLocalVirtualStrategy(host: () => HTMLDivElement | undefined, enabled: () => boolean): VirtualStrategy {
|
|
||||||
let virtualizer: Virtualizer | undefined
|
|
||||||
let root: Document | HTMLElement | undefined
|
|
||||||
|
|
||||||
const release = () => {
|
|
||||||
virtualizer?.cleanUp()
|
|
||||||
virtualizer = undefined
|
|
||||||
root = undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
get: () => {
|
|
||||||
if (!enabled()) {
|
|
||||||
release()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (typeof document === "undefined") return
|
|
||||||
|
|
||||||
const wrapper = host()
|
|
||||||
if (!wrapper) return
|
|
||||||
|
|
||||||
const next = scrollParent(wrapper) ?? document
|
|
||||||
if (virtualizer && root === next) return virtualizer
|
|
||||||
|
|
||||||
release()
|
|
||||||
virtualizer = new Virtualizer()
|
|
||||||
root = next
|
|
||||||
virtualizer.setup(next, next instanceof Document ? undefined : wrapper)
|
|
||||||
return virtualizer
|
|
||||||
},
|
|
||||||
cleanup: release,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createSharedVirtualStrategy(host: () => HTMLDivElement | undefined): VirtualStrategy {
|
|
||||||
let shared: NonNullable<ReturnType<typeof acquireVirtualizer>> | undefined
|
|
||||||
|
|
||||||
const release = () => {
|
|
||||||
shared?.release()
|
|
||||||
shared = undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
get: () => {
|
|
||||||
if (shared) return shared.virtualizer
|
|
||||||
|
|
||||||
const container = host()
|
|
||||||
if (!container) return
|
|
||||||
|
|
||||||
const result = acquireVirtualizer(container)
|
|
||||||
if (!result) return
|
|
||||||
shared = result
|
|
||||||
return result.virtualizer
|
|
||||||
},
|
|
||||||
cleanup: release,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseLine(node: HTMLElement) {
|
function parseLine(node: HTMLElement) {
|
||||||
if (!node.dataset.line) return
|
if (!node.dataset.line) return
|
||||||
const value = parseInt(node.dataset.line, 10)
|
const value = parseInt(node.dataset.line, 10)
|
||||||
@@ -688,7 +611,7 @@ function ViewerShell(props: {
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
function TextViewer<T>(props: TextFileProps<T>) {
|
function TextViewer<T>(props: TextFileProps<T>) {
|
||||||
let instance: PierreFile<T> | VirtualizedFile<T> | undefined
|
let instance: PierreFile<T> | undefined
|
||||||
let viewer!: Viewer
|
let viewer!: Viewer
|
||||||
|
|
||||||
const [local, others] = splitProps(props, textKeys)
|
const [local, others] = splitProps(props, textKeys)
|
||||||
@@ -707,34 +630,12 @@ function TextViewer<T>(props: TextFileProps<T>) {
|
|||||||
return Math.max(1, total)
|
return Math.max(1, total)
|
||||||
}
|
}
|
||||||
|
|
||||||
const bytes = createMemo(() => {
|
|
||||||
const value = local.file.contents as unknown
|
|
||||||
if (typeof value === "string") return value.length
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
return value.reduce(
|
|
||||||
(sum, part) => sum + (typeof part === "string" ? part.length + 1 : String(part).length + 1),
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (value == null) return 0
|
|
||||||
return String(value).length
|
|
||||||
})
|
|
||||||
|
|
||||||
const virtual = createMemo(() => bytes() > VIRTUALIZE_BYTES)
|
|
||||||
|
|
||||||
const virtuals = createLocalVirtualStrategy(() => viewer.wrapper, virtual)
|
|
||||||
|
|
||||||
const lineFromMouseEvent = (event: MouseEvent): MouseHit => mouseHit(event, parseLine)
|
const lineFromMouseEvent = (event: MouseEvent): MouseHit => mouseHit(event, parseLine)
|
||||||
|
|
||||||
const applySelection = (range: SelectedLineRange | null) => {
|
const applySelection = (range: SelectedLineRange | null) => {
|
||||||
const current = instance
|
const current = instance
|
||||||
if (!current) return false
|
if (!current) return false
|
||||||
|
|
||||||
if (virtual()) {
|
|
||||||
current.setSelectedLines(range)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
const root = viewer.getRoot()
|
const root = viewer.getRoot()
|
||||||
if (!root) return false
|
if (!root) return false
|
||||||
|
|
||||||
@@ -833,10 +734,7 @@ function TextViewer<T>(props: TextFileProps<T>) {
|
|||||||
const notify = () => {
|
const notify = () => {
|
||||||
notifyRendered({
|
notifyRendered({
|
||||||
viewer,
|
viewer,
|
||||||
isReady: (root) => {
|
isReady: (root) => root.querySelectorAll("[data-line]").length >= lineCount(),
|
||||||
if (virtual()) return root.querySelector("[data-line]") != null
|
|
||||||
return root.querySelectorAll("[data-line]").length >= lineCount()
|
|
||||||
},
|
|
||||||
onReady: () => {
|
onReady: () => {
|
||||||
applySelection(viewer.lastSelection)
|
applySelection(viewer.lastSelection)
|
||||||
viewer.find.refresh({ reset: true })
|
viewer.find.refresh({ reset: true })
|
||||||
@@ -855,17 +753,11 @@ function TextViewer<T>(props: TextFileProps<T>) {
|
|||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const opts = options()
|
const opts = options()
|
||||||
const workerPool = getWorkerPool("unified")
|
const workerPool = getWorkerPool("unified")
|
||||||
const isVirtual = virtual()
|
|
||||||
|
|
||||||
const virtualizer = virtuals.get()
|
|
||||||
|
|
||||||
renderViewer({
|
renderViewer({
|
||||||
viewer,
|
viewer,
|
||||||
current: instance,
|
current: instance,
|
||||||
create: () =>
|
create: () => new PierreFile<T>(opts, workerPool),
|
||||||
isVirtual && virtualizer
|
|
||||||
? new VirtualizedFile<T>(opts, virtualizer, codeMetrics, workerPool)
|
|
||||||
: new PierreFile<T>(opts, workerPool),
|
|
||||||
assign: (value) => {
|
assign: (value) => {
|
||||||
instance = value
|
instance = value
|
||||||
},
|
},
|
||||||
@@ -892,7 +784,6 @@ function TextViewer<T>(props: TextFileProps<T>) {
|
|||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
instance?.cleanUp()
|
instance?.cleanUp()
|
||||||
instance = undefined
|
instance = undefined
|
||||||
virtuals.cleanup()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return <ViewerShell mode="text" viewer={viewer} class={local.class} classList={local.classList} />
|
return <ViewerShell mode="text" viewer={viewer} class={local.class} classList={local.classList} />
|
||||||
@@ -988,8 +879,6 @@ function DiffViewer<T>(props: DiffFileProps<T>) {
|
|||||||
adapter,
|
adapter,
|
||||||
)
|
)
|
||||||
|
|
||||||
const virtuals = createSharedVirtualStrategy(() => viewer.container)
|
|
||||||
|
|
||||||
const large = createMemo(() => {
|
const large = createMemo(() => {
|
||||||
if (local.fileDiff) {
|
if (local.fileDiff) {
|
||||||
const before = local.fileDiff.deletionLines.join("")
|
const before = local.fileDiff.deletionLines.join("")
|
||||||
@@ -1052,7 +941,6 @@ function DiffViewer<T>(props: DiffFileProps<T>) {
|
|||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const opts = options()
|
const opts = options()
|
||||||
const workerPool = large() ? getWorkerPool("unified") : getWorkerPool(props.diffStyle)
|
const workerPool = large() ? getWorkerPool("unified") : getWorkerPool(props.diffStyle)
|
||||||
const virtualizer = virtuals.get()
|
|
||||||
const beforeContents = typeof local.before?.contents === "string" ? local.before.contents : ""
|
const beforeContents = typeof local.before?.contents === "string" ? local.before.contents : ""
|
||||||
const afterContents = typeof local.after?.contents === "string" ? local.after.contents : ""
|
const afterContents = typeof local.after?.contents === "string" ? local.after.contents : ""
|
||||||
const done = preserve(viewer)
|
const done = preserve(viewer)
|
||||||
@@ -1067,10 +955,7 @@ function DiffViewer<T>(props: DiffFileProps<T>) {
|
|||||||
renderViewer({
|
renderViewer({
|
||||||
viewer,
|
viewer,
|
||||||
current: instance,
|
current: instance,
|
||||||
create: () =>
|
create: () => new FileDiff<T>(opts, workerPool),
|
||||||
virtualizer
|
|
||||||
? new VirtualizedFileDiff<T>(opts, virtualizer, virtualMetrics, workerPool)
|
|
||||||
: new FileDiff<T>(opts, workerPool),
|
|
||||||
assign: (value) => {
|
assign: (value) => {
|
||||||
instance = value
|
instance = value
|
||||||
},
|
},
|
||||||
@@ -1108,7 +993,6 @@ function DiffViewer<T>(props: DiffFileProps<T>) {
|
|||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
instance?.cleanUp()
|
instance?.cleanUp()
|
||||||
instance = undefined
|
instance = undefined
|
||||||
virtuals.cleanup()
|
|
||||||
dragSide = undefined
|
dragSide = undefined
|
||||||
dragEndSide = undefined
|
dragEndSide = undefined
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import type { LineCommentEditorProps } from "./line-comment"
|
|||||||
import { normalize, text, type ViewDiff } from "./session-diff"
|
import { normalize, text, type ViewDiff } from "./session-diff"
|
||||||
|
|
||||||
const MAX_DIFF_CHANGED_LINES = 500
|
const MAX_DIFF_CHANGED_LINES = 500
|
||||||
const REVIEW_MOUNT_MARGIN = 300
|
|
||||||
|
|
||||||
export type SessionReviewDiffStyle = "unified" | "split"
|
export type SessionReviewDiffStyle = "unified" | "split"
|
||||||
|
|
||||||
@@ -139,14 +138,11 @@ type SessionReviewSelection = {
|
|||||||
export const SessionReview = (props: SessionReviewProps) => {
|
export const SessionReview = (props: SessionReviewProps) => {
|
||||||
let scroll: HTMLDivElement | undefined
|
let scroll: HTMLDivElement | undefined
|
||||||
let focusToken = 0
|
let focusToken = 0
|
||||||
let frame: number | undefined
|
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const fileComponent = useFileComponent()
|
const fileComponent = useFileComponent()
|
||||||
const anchors = new Map<string, HTMLElement>()
|
const anchors = new Map<string, HTMLElement>()
|
||||||
const nodes = new Map<string, HTMLDivElement>()
|
|
||||||
const [store, setStore] = createStore({
|
const [store, setStore] = createStore({
|
||||||
open: [] as string[],
|
open: [] as string[],
|
||||||
visible: {} as Record<string, boolean>,
|
|
||||||
force: {} as Record<string, boolean>,
|
force: {} as Record<string, boolean>,
|
||||||
selection: null as SessionReviewSelection | null,
|
selection: null as SessionReviewSelection | null,
|
||||||
commenting: null as SessionReviewSelection | null,
|
commenting: null as SessionReviewSelection | null,
|
||||||
@@ -174,44 +170,7 @@ export const SessionReview = (props: SessionReviewProps) => {
|
|||||||
const diffStyle = () => props.diffStyle ?? (props.split ? "split" : "unified")
|
const diffStyle = () => props.diffStyle ?? (props.split ? "split" : "unified")
|
||||||
const hasDiffs = () => files().length > 0
|
const hasDiffs = () => files().length > 0
|
||||||
|
|
||||||
const syncVisible = () => {
|
|
||||||
frame = undefined
|
|
||||||
if (!scroll) return
|
|
||||||
|
|
||||||
const root = scroll.getBoundingClientRect()
|
|
||||||
const top = root.top - REVIEW_MOUNT_MARGIN
|
|
||||||
const bottom = root.bottom + REVIEW_MOUNT_MARGIN
|
|
||||||
const openSet = new Set(open())
|
|
||||||
const next: Record<string, boolean> = {}
|
|
||||||
|
|
||||||
for (const [file, el] of nodes) {
|
|
||||||
if (!openSet.has(file)) continue
|
|
||||||
const rect = el.getBoundingClientRect()
|
|
||||||
if (rect.bottom < top || rect.top > bottom) continue
|
|
||||||
next[file] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const prev = untrack(() => store.visible)
|
|
||||||
const prevKeys = Object.keys(prev)
|
|
||||||
const nextKeys = Object.keys(next)
|
|
||||||
if (prevKeys.length === nextKeys.length && nextKeys.every((file) => prev[file])) return
|
|
||||||
setStore("visible", next)
|
|
||||||
}
|
|
||||||
|
|
||||||
const queue = () => {
|
|
||||||
if (frame !== undefined) return
|
|
||||||
frame = requestAnimationFrame(syncVisible)
|
|
||||||
}
|
|
||||||
|
|
||||||
const pinned = (file: string) =>
|
|
||||||
props.focusedComment?.file === file ||
|
|
||||||
props.focusedFile === file ||
|
|
||||||
selection()?.file === file ||
|
|
||||||
commenting()?.file === file ||
|
|
||||||
opened()?.file === file
|
|
||||||
|
|
||||||
const handleScroll: JSX.EventHandler<HTMLDivElement, Event> = (event) => {
|
const handleScroll: JSX.EventHandler<HTMLDivElement, Event> = (event) => {
|
||||||
queue()
|
|
||||||
const next = props.onScroll
|
const next = props.onScroll
|
||||||
if (!next) return
|
if (!next) return
|
||||||
if (Array.isArray(next)) {
|
if (Array.isArray(next)) {
|
||||||
@@ -222,21 +181,9 @@ export const SessionReview = (props: SessionReviewProps) => {
|
|||||||
;(next as JSX.EventHandler<HTMLDivElement, Event>)(event)
|
;(next as JSX.EventHandler<HTMLDivElement, Event>)(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
onCleanup(() => {
|
|
||||||
if (frame === undefined) return
|
|
||||||
cancelAnimationFrame(frame)
|
|
||||||
})
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
props.open
|
|
||||||
files()
|
|
||||||
queue()
|
|
||||||
})
|
|
||||||
|
|
||||||
const handleChange = (next: string[]) => {
|
const handleChange = (next: string[]) => {
|
||||||
props.onOpenChange?.(next)
|
props.onOpenChange?.(next)
|
||||||
if (props.open === undefined) setStore("open", next)
|
if (props.open === undefined) setStore("open", next)
|
||||||
queue()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleExpandOrCollapseAll = () => {
|
const handleExpandOrCollapseAll = () => {
|
||||||
@@ -350,7 +297,6 @@ export const SessionReview = (props: SessionReviewProps) => {
|
|||||||
viewportRef={(el) => {
|
viewportRef={(el) => {
|
||||||
scroll = el
|
scroll = el
|
||||||
props.scrollRef?.(el)
|
props.scrollRef?.(el)
|
||||||
queue()
|
|
||||||
}}
|
}}
|
||||||
onScroll={handleScroll}
|
onScroll={handleScroll}
|
||||||
classList={{
|
classList={{
|
||||||
@@ -363,11 +309,9 @@ export const SessionReview = (props: SessionReviewProps) => {
|
|||||||
<Accordion multiple value={open()} onChange={handleChange}>
|
<Accordion multiple value={open()} onChange={handleChange}>
|
||||||
<For each={items()}>
|
<For each={items()}>
|
||||||
{(diff) => {
|
{(diff) => {
|
||||||
let wrapper: HTMLDivElement | undefined
|
|
||||||
const file = diff.file
|
const file = diff.file
|
||||||
|
|
||||||
const expanded = createMemo(() => open().includes(file))
|
const expanded = createMemo(() => open().includes(file))
|
||||||
const mounted = createMemo(() => expanded() && (!!store.visible[file] || pinned(file)))
|
|
||||||
const force = () => !!store.force[file]
|
const force = () => !!store.force[file]
|
||||||
|
|
||||||
const comments = createMemo(() => grouped().get(file) ?? [])
|
const comments = createMemo(() => grouped().get(file) ?? [])
|
||||||
@@ -458,8 +402,6 @@ export const SessionReview = (props: SessionReviewProps) => {
|
|||||||
|
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
anchors.delete(file)
|
anchors.delete(file)
|
||||||
nodes.delete(file)
|
|
||||||
queue()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleLineSelected = (range: SelectedLineRange | null) => {
|
const handleLineSelected = (range: SelectedLineRange | null) => {
|
||||||
@@ -542,21 +484,11 @@ export const SessionReview = (props: SessionReviewProps) => {
|
|||||||
<div
|
<div
|
||||||
data-slot="session-review-diff-wrapper"
|
data-slot="session-review-diff-wrapper"
|
||||||
ref={(el) => {
|
ref={(el) => {
|
||||||
wrapper = el
|
|
||||||
anchors.set(file, el)
|
anchors.set(file, el)
|
||||||
nodes.set(file, el)
|
|
||||||
queue()
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Show when={expanded()}>
|
<Show when={expanded()}>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={!mounted() && !tooLarge()}>
|
|
||||||
<div
|
|
||||||
data-slot="session-review-diff-placeholder"
|
|
||||||
class="rounded-lg border border-border-weak-base bg-background-stronger/40"
|
|
||||||
style={{ height: "160px" }}
|
|
||||||
/>
|
|
||||||
</Match>
|
|
||||||
<Match when={tooLarge()}>
|
<Match when={tooLarge()}>
|
||||||
<div data-slot="session-review-large-diff">
|
<div data-slot="session-review-large-diff">
|
||||||
<div data-slot="session-review-large-diff-title">
|
<div data-slot="session-review-large-diff-title">
|
||||||
|
|||||||
@@ -1,100 +0,0 @@
|
|||||||
import { type VirtualFileMetrics, Virtualizer } from "@pierre/diffs"
|
|
||||||
|
|
||||||
type Target = {
|
|
||||||
key: Document | HTMLElement
|
|
||||||
root: Document | HTMLElement
|
|
||||||
content: HTMLElement | undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
type Entry = {
|
|
||||||
virtualizer: Virtualizer
|
|
||||||
refs: number
|
|
||||||
}
|
|
||||||
|
|
||||||
const cache = new WeakMap<Document | HTMLElement, Entry>()
|
|
||||||
|
|
||||||
export const virtualMetrics: Partial<VirtualFileMetrics> = {
|
|
||||||
lineHeight: 24,
|
|
||||||
hunkSeparatorHeight: 24,
|
|
||||||
fileGap: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
function scrollable(value: string) {
|
|
||||||
return value === "auto" || value === "scroll" || value === "overlay"
|
|
||||||
}
|
|
||||||
|
|
||||||
function scrollRoot(container: HTMLElement) {
|
|
||||||
let node = container.parentElement
|
|
||||||
while (node) {
|
|
||||||
const style = getComputedStyle(node)
|
|
||||||
if (scrollable(style.overflowY)) return node
|
|
||||||
node = node.parentElement
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function target(container: HTMLElement): Target | undefined {
|
|
||||||
if (typeof document === "undefined") return
|
|
||||||
|
|
||||||
const review = container.closest("[data-component='session-review']")
|
|
||||||
if (review instanceof HTMLElement) {
|
|
||||||
const root = scrollRoot(container) ?? review
|
|
||||||
const content = review.querySelector("[data-slot='session-review-container']")
|
|
||||||
return {
|
|
||||||
key: review,
|
|
||||||
root,
|
|
||||||
content: content instanceof HTMLElement ? content : undefined,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const root = scrollRoot(container)
|
|
||||||
if (root) {
|
|
||||||
const content = root.querySelector("[role='log']")
|
|
||||||
return {
|
|
||||||
key: root,
|
|
||||||
root,
|
|
||||||
content: content instanceof HTMLElement ? content : undefined,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
key: document,
|
|
||||||
root: document,
|
|
||||||
content: undefined,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function acquireVirtualizer(container: HTMLElement) {
|
|
||||||
const resolved = target(container)
|
|
||||||
if (!resolved) return
|
|
||||||
|
|
||||||
let entry = cache.get(resolved.key)
|
|
||||||
if (!entry) {
|
|
||||||
const virtualizer = new Virtualizer()
|
|
||||||
virtualizer.setup(resolved.root, resolved.content)
|
|
||||||
entry = {
|
|
||||||
virtualizer,
|
|
||||||
refs: 0,
|
|
||||||
}
|
|
||||||
cache.set(resolved.key, entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
entry.refs += 1
|
|
||||||
let done = false
|
|
||||||
|
|
||||||
return {
|
|
||||||
virtualizer: entry.virtualizer,
|
|
||||||
release() {
|
|
||||||
if (done) return
|
|
||||||
done = true
|
|
||||||
|
|
||||||
const current = cache.get(resolved.key)
|
|
||||||
if (!current) return
|
|
||||||
|
|
||||||
current.refs -= 1
|
|
||||||
if (current.refs > 0) return
|
|
||||||
|
|
||||||
current.virtualizer.cleanUp()
|
|
||||||
cache.delete(resolved.key)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user