diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index 44a1db253..0d6a7641a 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -15,7 +15,7 @@ import { import { createStore, produce } from "solid-js/store" import { createFocusSignal } from "@solid-primitives/active-element" import { useLocal } from "@/context/local" -import { useFile, type FileSelection } from "@/context/file" +import { selectionFromLines, useFile, type FileSelection } from "@/context/file" import { ContentPart, DEFAULT_PROMPT, @@ -163,6 +163,14 @@ export const PromptInput: Component = (props) => { if (!tab) return return files.pathFromTab(tab) }) + + const activeFileSelection = createMemo(() => { + const path = activeFile() + if (!path) return + const range = files.selectedLines(path) + if (!range) return + return selectionFromLines(range) + }) const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined)) const status = createMemo( () => @@ -1256,7 +1264,7 @@ export const PromptInput: Component = (props) => { const activePath = activeFile() if (activePath && prompt.context.activeTab()) { - addContextFile(activePath) + addContextFile(activePath, activeFileSelection()) } for (const item of prompt.context.items()) { @@ -1476,22 +1484,31 @@ export const PromptInput: Component = (props) => { - 0 || !!activeFile())}> -
+ 0 || !!activeFile()}> +
{(path) => ( -
- -
+
+ +
{getDirectory(path())} {getFilename(path())} + + {(sel) => ( + + {sel().startLine === sel().endLine + ? `:${sel().startLine}` + : `:${sel().startLine}-${sel().endLine}`} + + )} + {language.t("prompt.context.active")}
prompt.context.removeActive()} aria-label={language.t("prompt.context.removeActiveFile")} /> @@ -1501,7 +1518,7 @@ export const PromptInput: Component = (props) => {
@@ -1470,9 +1514,7 @@ export default function Page() { sync.session.history.loadMore(id) }} > - {historyLoading() - ? language.t("session.messages.loadingEarlier") - : language.t("session.messages.loadEarlier")} + {historyLoading() ? "Loading earlier messages..." : "Load earlier messages"}
@@ -1556,7 +1598,7 @@ export default function Page() { when={prompt.ready()} fallback={
- {handoff.prompt || language.t("prompt.loading")} + {handoff.prompt || "Loading prompt..."}
} > @@ -1608,7 +1650,7 @@ export default function Page() {
-
{language.t("session.tab.review")}
+
Review
{info()?.summary?.files ?? 0} @@ -1636,7 +1678,7 @@ export default function Page() { >
-
{language.t("session.tab.context")}
+
Context
@@ -1645,7 +1687,7 @@ export default function Page() {
@@ -1668,11 +1710,7 @@ export default function Page() { - {language.t("session.review.loadingChanges")} -
- } + fallback={
Loading changes...
} >
-
- {language.t("session.review.empty")} -
+
No changes in this session yet
@@ -1719,6 +1755,9 @@ export default function Page() { let scroll: HTMLDivElement | undefined let scrollFrame: number | undefined let pending: { x: number; y: number } | undefined + let codeScroll: HTMLElement[] = [] + + const [selectionPopoverTop, setSelectionPopoverTop] = createSignal() const path = createMemo(() => file.pathFromTab(tab)) const state = createMemo(() => { @@ -1775,28 +1814,78 @@ export default function Page() { return `L${sel.startLine}-${sel.endLine}` }) - const restoreScroll = (retries = 0) => { + const updateSelectionPopover = () => { const el = scroll - if (!el) return - - const s = view()?.scroll(tab) - if (!s) return - - // Wait for content to be scrollable - content may not have rendered yet - if (el.scrollHeight <= el.clientHeight && retries < 10) { - requestAnimationFrame(() => restoreScroll(retries + 1)) + if (!el) { + setSelectionPopoverTop(undefined) return } - if (el.scrollTop !== s.y) el.scrollTop = s.y - if (el.scrollLeft !== s.x) el.scrollLeft = s.x + const sel = selection() + if (!sel) { + setSelectionPopoverTop(undefined) + return + } + + const host = el.querySelector("diffs-container") + if (!(host instanceof HTMLElement)) { + setSelectionPopoverTop(undefined) + return + } + + const root = host.shadowRoot + if (!root) { + setSelectionPopoverTop(undefined) + return + } + + const marker = + (root.querySelector( + '[data-selected-line="last"], [data-selected-line="single"]', + ) as HTMLElement | null) ?? (root.querySelector("[data-selected-line]") as HTMLElement | null) + + if (!marker) { + setSelectionPopoverTop(undefined) + return + } + + const containerRect = el.getBoundingClientRect() + const markerRect = marker.getBoundingClientRect() + setSelectionPopoverTop(markerRect.bottom - containerRect.top + el.scrollTop + 8) } - const handleScroll = (event: Event & { currentTarget: HTMLDivElement }) => { - pending = { - x: event.currentTarget.scrollLeft, - y: event.currentTarget.scrollTop, - } + createEffect( + on( + selection, + (sel) => { + if (!sel) { + setSelectionPopoverTop(undefined) + return + } + + requestAnimationFrame(updateSelectionPopover) + }, + { defer: true }, + ), + ) + + const getCodeScroll = () => { + const el = scroll + if (!el) return [] + + const host = el.querySelector("diffs-container") + if (!(host instanceof HTMLElement)) return [] + + const root = host.shadowRoot + if (!root) return [] + + return Array.from(root.querySelectorAll("[data-code]")).filter( + (node): node is HTMLElement => node instanceof HTMLElement && node.clientWidth > 0, + ) + } + + const queueScrollUpdate = (next: { x: number; y: number }) => { + pending = next if (scrollFrame !== undefined) return scrollFrame = requestAnimationFrame(() => { @@ -1810,6 +1899,65 @@ export default function Page() { }) } + const handleCodeScroll = (event: Event) => { + const el = scroll + if (!el) return + + const target = event.currentTarget + if (!(target instanceof HTMLElement)) return + + queueScrollUpdate({ + x: target.scrollLeft, + y: el.scrollTop, + }) + } + + const syncCodeScroll = () => { + const next = getCodeScroll() + if (next.length === codeScroll.length && next.every((el, i) => el === codeScroll[i])) return + + for (const item of codeScroll) { + item.removeEventListener("scroll", handleCodeScroll) + } + + codeScroll = next + + for (const item of codeScroll) { + item.addEventListener("scroll", handleCodeScroll) + } + } + + const restoreScroll = () => { + const el = scroll + if (!el) return + + const s = view()?.scroll(tab) + if (!s) return + + syncCodeScroll() + + if (codeScroll.length > 0) { + for (const item of codeScroll) { + if (item.scrollLeft !== s.x) item.scrollLeft = s.x + } + } + + if (el.scrollTop !== s.y) el.scrollTop = s.y + + if (codeScroll.length > 0) return + + if (el.scrollLeft !== s.x) el.scrollLeft = s.x + } + + const handleScroll = (event: Event & { currentTarget: HTMLDivElement }) => { + if (codeScroll.length === 0) syncCodeScroll() + + queueScrollUpdate({ + x: codeScroll[0]?.scrollLeft ?? event.currentTarget.scrollLeft, + y: event.currentTarget.scrollTop, + }) + } + createEffect( on( () => state()?.loaded, @@ -1844,6 +1992,10 @@ export default function Page() { ) onCleanup(() => { + for (const item of codeScroll) { + item.removeEventListener("scroll", handleCodeScroll) + } + if (scrollFrame === undefined) return cancelAnimationFrame(scrollFrame) }) @@ -1851,93 +2003,115 @@ export default function Page() { return ( { scroll = el restoreScroll() + updateSelectionPopover() }} onScroll={handleScroll} > - + {(sel) => ( - )} - - -
- {path()} -
-
- -
- { - const p = path() - if (!p) return - file.setSelectedLines(p, range) - }} - overflow="scroll" - class="select-text" - /> - -
- {path()} -
-
-
-
- +
+ + +
+ {path()} requestAnimationFrame(restoreScroll)} + /> +
+
+ +
{ + requestAnimationFrame(restoreScroll) + requestAnimationFrame(updateSelectionPopover) + }} onLineSelected={(range: SelectedLineRange | null) => { const p = path() if (!p) return file.setSelectedLines(p, range) }} overflow="scroll" - class="select-text pb-40" + class="select-text" /> - - -
{language.t("common.loading")}...
-
- - {(err) =>
{err()}
} -
- - + +
+ {path()} +
+
+
+
+ + { + requestAnimationFrame(restoreScroll) + requestAnimationFrame(updateSelectionPopover) + }} + onLineSelected={(range: SelectedLineRange | null) => { + const p = path() + if (!p) return + file.setSelectedLines(p, range) + }} + overflow="scroll" + class="select-text pb-40" + /> + + +
{language.t("common.loading")}...
+
+ + {(err) =>
{err()}
} +
+
) }} @@ -1990,11 +2164,9 @@ export default function Page() { )}
-
{language.t("common.loading")}...
-
-
- {language.t("terminal.loading")} +
Loading...
+
Loading terminal...
} > diff --git a/packages/ui/src/components/code.tsx b/packages/ui/src/components/code.tsx index ed7db368c..c6f702fb5 100644 --- a/packages/ui/src/components/code.tsx +++ b/packages/ui/src/components/code.tsx @@ -9,6 +9,7 @@ export type CodeProps = FileOptions & { file: FileContents annotations?: LineAnnotation[] selectedLines?: SelectedLineRange | null + onRendered?: () => void class?: string classList?: ComponentProps<"div">["classList"] } @@ -45,8 +46,32 @@ function findSide(node: Node | null): SelectionSide | undefined { export function Code(props: CodeProps) { let container!: HTMLDivElement + let observer: MutationObserver | undefined + let renderToken = 0 + let selectionFrame: number | undefined + let dragFrame: number | undefined + let dragStart: number | undefined + let dragEnd: number | undefined + let dragMoved = false - const [local, others] = splitProps(props, ["file", "class", "classList", "annotations", "selectedLines"]) + const [local, others] = splitProps(props, [ + "file", + "class", + "classList", + "annotations", + "selectedLines", + "onRendered", + ]) + + const handleLineClick: FileOptions["onLineClick"] = (info) => { + props.onLineClick?.(info) + + if (props.enableLineSelection !== true) return + if (info.numberColumn) return + if (!local.selectedLines) return + + file().setSelectedLines(null) + } const file = createMemo( () => @@ -54,6 +79,7 @@ export function Code(props: CodeProps) { { ...createDefaultOptions("unified"), ...others, + onLineClick: props.enableLineSelection === true || props.onLineClick ? handleLineClick : undefined, }, getWorkerPool("unified"), ), @@ -69,37 +95,218 @@ export function Code(props: CodeProps) { return root } - const handleMouseUp = () => { - if (props.enableLineSelection !== true) return + const notifyRendered = () => { + if (!local.onRendered) return + observer?.disconnect() + observer = undefined + renderToken++ + + const token = renderToken + + const lines = (() => { + const text = local.file.contents + const total = text.split("\n").length - (text.endsWith("\n") ? 1 : 0) + return Math.max(1, total) + })() + + const isReady = (root: ShadowRoot) => root.querySelectorAll("[data-line]").length >= lines + + const notify = () => { + if (token !== renderToken) return + + observer?.disconnect() + observer = undefined + requestAnimationFrame(() => { + if (token !== renderToken) return + local.onRendered?.() + }) + } + + const root = getRoot() + if (root && isReady(root)) { + notify() + return + } + + if (typeof MutationObserver === "undefined") return + + const observeRoot = (root: ShadowRoot) => { + if (isReady(root)) { + notify() + return + } + + observer?.disconnect() + observer = new MutationObserver(() => { + if (token !== renderToken) return + if (!isReady(root)) return + + notify() + }) + + observer.observe(root, { childList: true, subtree: true }) + } + + if (root) { + observeRoot(root) + return + } + + observer = new MutationObserver(() => { + if (token !== renderToken) return + + const root = getRoot() + if (!root) return + + observeRoot(root) + }) + + observer.observe(container, { childList: true, subtree: true }) + } + + const updateSelection = () => { const root = getRoot() if (!root) return - const selection = window.getSelection() + const selection = + (root as unknown as { getSelection?: () => Selection | null }).getSelection?.() ?? window.getSelection() if (!selection || selection.isCollapsed) return - const anchor = selection.anchorNode - const focus = selection.focusNode - if (!anchor || !focus) return - if (!root.contains(anchor) || !root.contains(focus)) return + const domRange = + ( + selection as unknown as { + getComposedRanges?: (options?: { shadowRoots?: ShadowRoot[] }) => Range[] + } + ).getComposedRanges?.({ shadowRoots: [root] })?.[0] ?? + (selection.rangeCount > 0 ? selection.getRangeAt(0) : undefined) - const start = findLineNumber(anchor) - const end = findLineNumber(focus) + const startNode = domRange?.startContainer ?? selection.anchorNode + const endNode = domRange?.endContainer ?? selection.focusNode + if (!startNode || !endNode) return + + if (!root.contains(startNode) || !root.contains(endNode)) return + + const start = findLineNumber(startNode) + const end = findLineNumber(endNode) if (start === undefined || end === undefined) return - const startSide = findSide(anchor) - const endSide = findSide(focus) + const startSide = findSide(startNode) + const endSide = findSide(endNode) const side = startSide ?? endSide - const range: SelectedLineRange = { + const selected: SelectedLineRange = { start, end, } - if (side) range.side = side - if (endSide && side && endSide !== side) range.endSide = endSide + if (side) selected.side = side + if (endSide && side && endSide !== side) selected.endSide = endSide - file().setSelectedLines(range) + file().setSelectedLines(selected) + } + + const scheduleSelectionUpdate = () => { + if (selectionFrame !== undefined) return + + selectionFrame = requestAnimationFrame(() => { + selectionFrame = undefined + updateSelection() + }) + } + + const updateDragSelection = () => { + if (dragStart === undefined || dragEnd === undefined) return + + const start = Math.min(dragStart, dragEnd) + const end = Math.max(dragStart, dragEnd) + + file().setSelectedLines({ start, end }) + } + + const scheduleDragUpdate = () => { + if (dragFrame !== undefined) return + + dragFrame = requestAnimationFrame(() => { + dragFrame = undefined + updateDragSelection() + }) + } + + const lineFromMouseEvent = (event: MouseEvent) => { + const path = event.composedPath() + + let numberColumn = false + let line: number | undefined + + for (const item of path) { + if (!(item instanceof HTMLElement)) continue + + numberColumn = numberColumn || item.dataset.columnNumber != null + + if (line === undefined && item.dataset.line) { + const parsed = parseInt(item.dataset.line, 10) + if (!Number.isNaN(parsed)) line = parsed + } + + if (numberColumn && line !== undefined) break + } + + return { line, numberColumn } + } + + const handleMouseDown = (event: MouseEvent) => { + if (props.enableLineSelection !== true) return + if (event.button !== 0) return + + const { line, numberColumn } = lineFromMouseEvent(event) + if (numberColumn) return + if (line === undefined) return + + dragStart = line + dragEnd = line + dragMoved = false + } + + const handleMouseMove = (event: MouseEvent) => { + if (props.enableLineSelection !== true) return + if (dragStart === undefined) return + + if ((event.buttons & 1) === 0) { + dragStart = undefined + dragEnd = undefined + dragMoved = false + return + } + + const { line } = lineFromMouseEvent(event) + if (line === undefined) return + + dragEnd = line + dragMoved = true + scheduleDragUpdate() + } + + const handleMouseUp = () => { + if (props.enableLineSelection !== true) return + + if (dragStart !== undefined) { + if (dragMoved) scheduleDragUpdate() + dragStart = undefined + dragEnd = undefined + dragMoved = false + } + + scheduleSelectionUpdate() + } + + const handleSelectionChange = () => { + if (props.enableLineSelection !== true) return + + const selection = window.getSelection() + if (!selection || selection.isCollapsed) return + + scheduleSelectionUpdate() } createEffect(() => { @@ -111,12 +318,17 @@ export function Code(props: CodeProps) { }) createEffect(() => { + observer?.disconnect() + observer = undefined + container.innerHTML = "" file().render({ file: local.file, lineAnnotations: local.annotations, containerWrapper: container, }) + + notifyRendered() }) createEffect(() => { @@ -126,13 +338,37 @@ export function Code(props: CodeProps) { createEffect(() => { if (props.enableLineSelection !== true) return - container.addEventListener("mouseup", handleMouseUp) + container.addEventListener("mousedown", handleMouseDown) + container.addEventListener("mousemove", handleMouseMove) + window.addEventListener("mouseup", handleMouseUp) + document.addEventListener("selectionchange", handleSelectionChange) onCleanup(() => { - container.removeEventListener("mouseup", handleMouseUp) + container.removeEventListener("mousedown", handleMouseDown) + container.removeEventListener("mousemove", handleMouseMove) + window.removeEventListener("mouseup", handleMouseUp) + document.removeEventListener("selectionchange", handleSelectionChange) }) }) + onCleanup(() => { + observer?.disconnect() + + if (selectionFrame !== undefined) { + cancelAnimationFrame(selectionFrame) + selectionFrame = undefined + } + + if (dragFrame !== undefined) { + cancelAnimationFrame(dragFrame) + dragFrame = undefined + } + + dragStart = undefined + dragEnd = undefined + dragMoved = false + }) + return (
(props: DiffProps) { let container!: HTMLDivElement - const [local, others] = splitProps(props, ["before", "after", "class", "classList", "annotations"]) + let observer: MutationObserver | undefined + let renderToken = 0 + + const [local, others] = splitProps(props, ["before", "after", "class", "classList", "annotations", "onRendered"]) const mobile = createMediaQuery("(max-width: 640px)") @@ -25,6 +28,95 @@ export function Diff(props: DiffProps) { let instance: FileDiff | undefined + const getRoot = () => { + const host = container.querySelector("diffs-container") + if (!(host instanceof HTMLElement)) return + + const root = host.shadowRoot + if (!root) return + + return root + } + + const notifyRendered = () => { + if (!local.onRendered) return + + observer?.disconnect() + observer = undefined + renderToken++ + + const token = renderToken + let settle = 0 + + const isReady = (root: ShadowRoot) => root.querySelector("[data-line]") != null + + const notify = () => { + if (token !== renderToken) return + + observer?.disconnect() + observer = undefined + requestAnimationFrame(() => { + if (token !== renderToken) return + local.onRendered?.() + }) + } + + const schedule = () => { + settle++ + const current = settle + + requestAnimationFrame(() => { + if (token !== renderToken) return + if (current !== settle) return + + requestAnimationFrame(() => { + if (token !== renderToken) return + if (current !== settle) return + + notify() + }) + }) + } + + const observeRoot = (root: ShadowRoot) => { + observer?.disconnect() + observer = new MutationObserver(() => { + if (token !== renderToken) return + if (!isReady(root)) return + + schedule() + }) + + observer.observe(root, { childList: true, subtree: true }) + + if (!isReady(root)) return + schedule() + } + + const root = getRoot() + if (typeof MutationObserver === "undefined") { + if (!root || !isReady(root)) return + local.onRendered() + return + } + + if (root) { + observeRoot(root) + return + } + + observer = new MutationObserver(() => { + if (token !== renderToken) return + + const root = getRoot() + if (!root) return + + observeRoot(root) + }) + + observer.observe(container, { childList: true, subtree: true }) + } + createEffect(() => { const opts = options() const workerPool = getWorkerPool(props.diffStyle) @@ -50,9 +142,12 @@ export function Diff(props: DiffProps) { lineAnnotations: annotations, containerWrapper: container, }) + + notifyRendered() }) onCleanup(() => { + observer?.disconnect() instance?.cleanUp() }) diff --git a/packages/ui/src/components/session-review.tsx b/packages/ui/src/components/session-review.tsx index 2c8de9aa4..c47d11d08 100644 --- a/packages/ui/src/components/session-review.tsx +++ b/packages/ui/src/components/session-review.tsx @@ -22,6 +22,7 @@ export interface SessionReviewProps { split?: boolean diffStyle?: SessionReviewDiffStyle onDiffStyleChange?: (diffStyle: SessionReviewDiffStyle) => void + onDiffRendered?: () => void open?: string[] onOpenChange?: (open: string[]) => void scrollRef?: (el: HTMLDivElement) => void @@ -346,6 +347,7 @@ export const SessionReview = (props: SessionReviewProps) => { component={diffComponent} preloadedDiff={diff.preloaded} diffStyle={diffStyle()} + onRendered={props.onDiffRendered} before={{ name: diff.file!, contents: beforeText(), diff --git a/packages/ui/src/pierre/index.ts b/packages/ui/src/pierre/index.ts index 824d96b11..38bf6c854 100644 --- a/packages/ui/src/pierre/index.ts +++ b/packages/ui/src/pierre/index.ts @@ -5,6 +5,7 @@ export type DiffProps = FileDiffOptions & { before: FileContents after: FileContents annotations?: DiffLineAnnotation[] + onRendered?: () => void class?: string classList?: ComponentProps<"div">["classList"] } @@ -18,9 +19,9 @@ const unsafeCSS = ` --diffs-bg-separator: var(--diffs-bg-separator-override, light-dark( color-mix(in lab, var(--diffs-bg) 96%, var(--diffs-mixer)), color-mix(in lab, var(--diffs-bg) 85%, var(--diffs-mixer)))); --diffs-fg: light-dark(var(--diffs-light), var(--diffs-dark)); --diffs-fg-number: var(--diffs-fg-number-override, light-dark(color-mix(in lab, var(--diffs-fg) 65%, var(--diffs-bg)), color-mix(in lab, var(--diffs-fg) 65%, var(--diffs-bg)))); - --diffs-deletion-base: var(--diffs-deletion-color-override, light-dark(var(--diffs-light-deletion-color, var(--diffs-deletion-color, rgb(255, 0, 0))), var(--diffs-dark-deletion-color, var(--diffs-deletion-color, rgb(255, 0, 0))))); - --diffs-addition-base: var(--diffs-addition-color-override, light-dark(var(--diffs-light-addition-color, var(--diffs-addition-color, rgb(0, 255, 0))), var(--diffs-dark-addition-color, var(--diffs-addition-color, rgb(0, 255, 0))))); - --diffs-modified-base: var(--diffs-modified-color-override, light-dark(var(--diffs-light-modified-color, var(--diffs-modified-color, rgb(0, 0, 255))), var(--diffs-dark-modified-color, var(--diffs-modified-color, rgb(0, 0, 255))))); + --diffs-deletion-base: var(--syntax-diff-delete); + --diffs-addition-base: var(--syntax-diff-add); + --diffs-modified-base: var(--syntax-diff-unknown); --diffs-bg-deletion: var(--diffs-bg-deletion-override, light-dark( color-mix(in lab, var(--diffs-bg) 98%, var(--diffs-deletion-base)), color-mix(in lab, var(--diffs-bg) 92%, var(--diffs-deletion-base)))); --diffs-bg-deletion-number: var(--diffs-bg-deletion-number-override, light-dark( color-mix(in lab, var(--diffs-bg) 91%, var(--diffs-deletion-base)), color-mix(in lab, var(--diffs-bg) 85%, var(--diffs-deletion-base)))); --diffs-bg-deletion-hover: var(--diffs-bg-deletion-hover-override, light-dark( color-mix(in lab, var(--diffs-bg) 80%, var(--diffs-deletion-base)), color-mix(in lab, var(--diffs-bg) 75%, var(--diffs-deletion-base)))); @@ -29,10 +30,15 @@ const unsafeCSS = ` --diffs-bg-addition-number: var(--diffs-bg-addition-number-override, light-dark( color-mix(in lab, var(--diffs-bg) 91%, var(--diffs-addition-base)), color-mix(in lab, var(--diffs-bg) 85%, var(--diffs-addition-base)))); --diffs-bg-addition-hover: var(--diffs-bg-addition-hover-override, light-dark( color-mix(in lab, var(--diffs-bg) 80%, var(--diffs-addition-base)), color-mix(in lab, var(--diffs-bg) 70%, var(--diffs-addition-base)))); --diffs-bg-addition-emphasis: var(--diffs-bg-addition-emphasis-override, light-dark(rgb(from var(--diffs-addition-base) r g b / 0.07), rgb(from var(--diffs-addition-base) r g b / 0.1))); - --diffs-selection-base: var(--diffs-modified-base); + --diffs-selection-base: var(--text-interactive-base); --diffs-selection-number-fg: light-dark( color-mix(in lab, var(--diffs-selection-base) 65%, var(--diffs-mixer)), color-mix(in lab, var(--diffs-selection-base) 75%, var(--diffs-mixer))); - --diffs-bg-selection: var(--diffs-bg-selection-override, light-dark( color-mix(in lab, var(--diffs-bg) 82%, var(--diffs-selection-base)), color-mix(in lab, var(--diffs-bg) 75%, var(--diffs-selection-base)))); - --diffs-bg-selection-number: var(--diffs-bg-selection-number-override, light-dark( color-mix(in lab, var(--diffs-bg) 75%, var(--diffs-selection-base)), color-mix(in lab, var(--diffs-bg) 60%, var(--diffs-selection-base)))); + --diffs-bg-selection: var(--diffs-bg-selection-override, rgb(from var(--diffs-selection-base) r g b / 0.18)); + --diffs-bg-selection-number: var(--diffs-bg-selection-number-override, rgb(from var(--diffs-selection-base) r g b / 0.22)); + --diffs-bg-selection-text: rgb(from var(--diffs-selection-base) r g b / 0.12); +} + +[data-diffs] ::selection { + background-color: var(--diffs-bg-selection-text); } [data-diffs-header], @@ -57,6 +63,9 @@ const unsafeCSS = ` [data-separator-content] { height: 24px !important; } + [data-column-number] { + background-color: var(--background-stronger); + } [data-code] { overflow-x: auto !important; }