wip(app): line selection

This commit is contained in:
Adam
2026-01-04 15:40:25 -06:00
parent 2e53697da0
commit 640d1f1ecc
7 changed files with 788 additions and 261 deletions

View File

@@ -15,7 +15,7 @@ import {
import { createStore, produce } from "solid-js/store" import { createStore, produce } from "solid-js/store"
import { createFocusSignal } from "@solid-primitives/active-element" import { createFocusSignal } from "@solid-primitives/active-element"
import { useLocal } from "@/context/local" import { useLocal } from "@/context/local"
import { useFile, type FileSelection } from "@/context/file" import { selectionFromLines, useFile, type FileSelection } from "@/context/file"
import { import {
ContentPart, ContentPart,
DEFAULT_PROMPT, DEFAULT_PROMPT,
@@ -163,6 +163,14 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
if (!tab) return if (!tab) return
return files.pathFromTab(tab) 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 info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined))
const status = createMemo( const status = createMemo(
() => () =>
@@ -1256,7 +1264,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
const activePath = activeFile() const activePath = activeFile()
if (activePath && prompt.context.activeTab()) { if (activePath && prompt.context.activeTab()) {
addContextFile(activePath) addContextFile(activePath, activeFileSelection())
} }
for (const item of prompt.context.items()) { for (const item of prompt.context.items()) {
@@ -1476,22 +1484,31 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
</div> </div>
</div> </div>
</Show> </Show>
<Show when={false && (prompt.context.items().length > 0 || !!activeFile())}> <Show when={prompt.context.items().length > 0 || !!activeFile()}>
<div class="flex flex-wrap items-center gap-2 px-3 pt-3"> <div class="flex flex-wrap items-center gap-1.5 px-3 pt-3">
<Show when={prompt.context.activeTab() ? activeFile() : undefined}> <Show when={prompt.context.activeTab() ? activeFile() : undefined}>
{(path) => ( {(path) => (
<div class="flex items-center gap-2 px-2 py-1 rounded-md bg-surface-base border border-border-base max-w-full"> <div class="flex items-center gap-1.5 px-1.5 py-0.5 rounded-md bg-surface-base border border-border-base max-w-full">
<FileIcon node={{ path: path(), type: "file" }} class="shrink-0 size-4" /> <FileIcon node={{ path: path(), type: "file" }} class="shrink-0 size-3.5" />
<div class="flex items-center text-12-regular min-w-0"> <div class="flex items-center text-11-regular min-w-0">
<span class="text-text-weak whitespace-nowrap truncate min-w-0">{getDirectory(path())}</span> <span class="text-text-weak whitespace-nowrap truncate min-w-0">{getDirectory(path())}</span>
<span class="text-text-strong whitespace-nowrap">{getFilename(path())}</span> <span class="text-text-strong whitespace-nowrap">{getFilename(path())}</span>
<Show when={activeFileSelection()}>
{(sel) => (
<span class="text-text-weak whitespace-nowrap ml-1">
{sel().startLine === sel().endLine
? `:${sel().startLine}`
: `:${sel().startLine}-${sel().endLine}`}
</span>
)}
</Show>
<span class="text-text-weak whitespace-nowrap ml-1">{language.t("prompt.context.active")}</span> <span class="text-text-weak whitespace-nowrap ml-1">{language.t("prompt.context.active")}</span>
</div> </div>
<IconButton <IconButton
type="button" type="button"
icon="close" icon="close"
variant="ghost" variant="ghost"
class="h-6 w-6" class="h-5 w-5"
onClick={() => prompt.context.removeActive()} onClick={() => prompt.context.removeActive()}
aria-label={language.t("prompt.context.removeActiveFile")} aria-label={language.t("prompt.context.removeActiveFile")}
/> />
@@ -1501,7 +1518,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
<Show when={!prompt.context.activeTab() && !!activeFile()}> <Show when={!prompt.context.activeTab() && !!activeFile()}>
<button <button
type="button" type="button"
class="flex items-center gap-2 px-2 py-1 rounded-md bg-surface-base border border-border-base text-12-regular text-text-weak hover:bg-surface-raised-base-hover" class="flex items-center gap-1.5 px-1.5 py-0.5 rounded-md bg-surface-base border border-border-base text-11-regular text-text-weak hover:bg-surface-raised-base-hover"
onClick={() => prompt.context.addActive()} onClick={() => prompt.context.addActive()}
> >
<Icon name="plus-small" size="small" /> <Icon name="plus-small" size="small" />
@@ -1510,9 +1527,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
</Show> </Show>
<For each={prompt.context.items()}> <For each={prompt.context.items()}>
{(item) => ( {(item) => (
<div class="flex items-center gap-2 px-2 py-1 rounded-md bg-surface-base border border-border-base max-w-full"> <div class="flex items-center gap-1.5 px-1.5 py-0.5 rounded-md bg-surface-base border border-border-base max-w-full">
<FileIcon node={{ path: item.path, type: "file" }} class="shrink-0 size-4" /> <FileIcon node={{ path: item.path, type: "file" }} class="shrink-0 size-3.5" />
<div class="flex items-center text-12-regular min-w-0"> <div class="flex items-center text-11-regular min-w-0">
<span class="text-text-weak whitespace-nowrap truncate min-w-0">{getDirectory(item.path)}</span> <span class="text-text-weak whitespace-nowrap truncate min-w-0">{getDirectory(item.path)}</span>
<span class="text-text-strong whitespace-nowrap">{getFilename(item.path)}</span> <span class="text-text-strong whitespace-nowrap">{getFilename(item.path)}</span>
<Show when={item.selection}> <Show when={item.selection}>
@@ -1529,7 +1546,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
type="button" type="button"
icon="close" icon="close"
variant="ghost" variant="ghost"
class="h-6 w-6" class="h-5 w-5"
onClick={() => prompt.context.remove(item.key)} onClick={() => prompt.context.remove(item.key)}
aria-label={language.t("prompt.context.removeFile")} aria-label={language.t("prompt.context.removeFile")}
/> />

View File

@@ -282,7 +282,9 @@ export function SessionContextTab(props: SessionContextTabProps) {
} }
}) })
return <Code file={file()} overflow="wrap" class="select-text" /> return (
<Code file={file()} overflow="wrap" class="select-text" onRendered={() => requestAnimationFrame(restoreScroll)} />
)
} }
function RawMessage(msgProps: { message: Message }) { function RawMessage(msgProps: { message: Message }) {
@@ -314,19 +316,13 @@ export function SessionContextTab(props: SessionContextTabProps) {
let frame: number | undefined let frame: number | undefined
let pending: { x: number; y: number } | undefined let pending: { x: number; y: number } | undefined
const restoreScroll = (retries = 0) => { const restoreScroll = () => {
const el = scroll const el = scroll
if (!el) return if (!el) return
const s = props.view()?.scroll("context") const s = props.view()?.scroll("context")
if (!s) return 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))
return
}
if (el.scrollTop !== s.y) el.scrollTop = s.y if (el.scrollTop !== s.y) el.scrollTop = s.y
if (el.scrollLeft !== s.x) el.scrollLeft = s.x if (el.scrollLeft !== s.x) el.scrollLeft = s.x
} }

File diff suppressed because it is too large Load Diff

View File

@@ -9,6 +9,7 @@ export type CodeProps<T = {}> = FileOptions<T> & {
file: FileContents file: FileContents
annotations?: LineAnnotation<T>[] annotations?: LineAnnotation<T>[]
selectedLines?: SelectedLineRange | null selectedLines?: SelectedLineRange | null
onRendered?: () => void
class?: string class?: string
classList?: ComponentProps<"div">["classList"] classList?: ComponentProps<"div">["classList"]
} }
@@ -45,8 +46,32 @@ function findSide(node: Node | null): SelectionSide | undefined {
export function Code<T>(props: CodeProps<T>) { export function Code<T>(props: CodeProps<T>) {
let container!: HTMLDivElement 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<T>["onLineClick"] = (info) => {
props.onLineClick?.(info)
if (props.enableLineSelection !== true) return
if (info.numberColumn) return
if (!local.selectedLines) return
file().setSelectedLines(null)
}
const file = createMemo( const file = createMemo(
() => () =>
@@ -54,6 +79,7 @@ export function Code<T>(props: CodeProps<T>) {
{ {
...createDefaultOptions<T>("unified"), ...createDefaultOptions<T>("unified"),
...others, ...others,
onLineClick: props.enableLineSelection === true || props.onLineClick ? handleLineClick : undefined,
}, },
getWorkerPool("unified"), getWorkerPool("unified"),
), ),
@@ -69,37 +95,218 @@ export function Code<T>(props: CodeProps<T>) {
return root return root
} }
const handleMouseUp = () => { const notifyRendered = () => {
if (props.enableLineSelection !== true) return 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() const root = getRoot()
if (!root) return if (!root) return
const selection = window.getSelection() const selection =
(root as unknown as { getSelection?: () => Selection | null }).getSelection?.() ?? window.getSelection()
if (!selection || selection.isCollapsed) return if (!selection || selection.isCollapsed) return
const anchor = selection.anchorNode const domRange =
const focus = selection.focusNode (
if (!anchor || !focus) return selection as unknown as {
if (!root.contains(anchor) || !root.contains(focus)) return getComposedRanges?: (options?: { shadowRoots?: ShadowRoot[] }) => Range[]
}
).getComposedRanges?.({ shadowRoots: [root] })?.[0] ??
(selection.rangeCount > 0 ? selection.getRangeAt(0) : undefined)
const start = findLineNumber(anchor) const startNode = domRange?.startContainer ?? selection.anchorNode
const end = findLineNumber(focus) 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 if (start === undefined || end === undefined) return
const startSide = findSide(anchor) const startSide = findSide(startNode)
const endSide = findSide(focus) const endSide = findSide(endNode)
const side = startSide ?? endSide const side = startSide ?? endSide
const range: SelectedLineRange = { const selected: SelectedLineRange = {
start, start,
end, end,
} }
if (side) range.side = side if (side) selected.side = side
if (endSide && side && endSide !== side) range.endSide = endSide 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(() => { createEffect(() => {
@@ -111,12 +318,17 @@ export function Code<T>(props: CodeProps<T>) {
}) })
createEffect(() => { createEffect(() => {
observer?.disconnect()
observer = undefined
container.innerHTML = "" container.innerHTML = ""
file().render({ file().render({
file: local.file, file: local.file,
lineAnnotations: local.annotations, lineAnnotations: local.annotations,
containerWrapper: container, containerWrapper: container,
}) })
notifyRendered()
}) })
createEffect(() => { createEffect(() => {
@@ -126,13 +338,37 @@ export function Code<T>(props: CodeProps<T>) {
createEffect(() => { createEffect(() => {
if (props.enableLineSelection !== true) return 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(() => { 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 ( return (
<div <div
data-component="code" data-component="code"

View File

@@ -7,7 +7,10 @@ import { getWorkerPool } from "../pierre/worker"
export function Diff<T>(props: DiffProps<T>) { export function Diff<T>(props: DiffProps<T>) {
let container!: HTMLDivElement 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)") const mobile = createMediaQuery("(max-width: 640px)")
@@ -25,6 +28,95 @@ export function Diff<T>(props: DiffProps<T>) {
let instance: FileDiff<T> | undefined let instance: FileDiff<T> | 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(() => { createEffect(() => {
const opts = options() const opts = options()
const workerPool = getWorkerPool(props.diffStyle) const workerPool = getWorkerPool(props.diffStyle)
@@ -50,9 +142,12 @@ export function Diff<T>(props: DiffProps<T>) {
lineAnnotations: annotations, lineAnnotations: annotations,
containerWrapper: container, containerWrapper: container,
}) })
notifyRendered()
}) })
onCleanup(() => { onCleanup(() => {
observer?.disconnect()
instance?.cleanUp() instance?.cleanUp()
}) })

View File

@@ -22,6 +22,7 @@ export interface SessionReviewProps {
split?: boolean split?: boolean
diffStyle?: SessionReviewDiffStyle diffStyle?: SessionReviewDiffStyle
onDiffStyleChange?: (diffStyle: SessionReviewDiffStyle) => void onDiffStyleChange?: (diffStyle: SessionReviewDiffStyle) => void
onDiffRendered?: () => void
open?: string[] open?: string[]
onOpenChange?: (open: string[]) => void onOpenChange?: (open: string[]) => void
scrollRef?: (el: HTMLDivElement) => void scrollRef?: (el: HTMLDivElement) => void
@@ -346,6 +347,7 @@ export const SessionReview = (props: SessionReviewProps) => {
component={diffComponent} component={diffComponent}
preloadedDiff={diff.preloaded} preloadedDiff={diff.preloaded}
diffStyle={diffStyle()} diffStyle={diffStyle()}
onRendered={props.onDiffRendered}
before={{ before={{
name: diff.file!, name: diff.file!,
contents: beforeText(), contents: beforeText(),

View File

@@ -5,6 +5,7 @@ export type DiffProps<T = {}> = FileDiffOptions<T> & {
before: FileContents before: FileContents
after: FileContents after: FileContents
annotations?: DiffLineAnnotation<T>[] annotations?: DiffLineAnnotation<T>[]
onRendered?: () => void
class?: string class?: string
classList?: ComponentProps<"div">["classList"] 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-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: 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-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-deletion-base: var(--syntax-diff-delete);
--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-addition-base: var(--syntax-diff-add);
--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-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: 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-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)))); --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-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-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-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-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: 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, 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-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], [data-diffs-header],
@@ -57,6 +63,9 @@ const unsafeCSS = `
[data-separator-content] { [data-separator-content] {
height: 24px !important; height: 24px !important;
} }
[data-column-number] {
background-color: var(--background-stronger);
}
[data-code] { [data-code] {
overflow-x: auto !important; overflow-x: auto !important;
} }