diff options
| author | Adam <[email protected]> | 2026-01-01 08:48:35 -0600 |
|---|---|---|
| committer | Adam <[email protected]> | 2026-01-01 21:03:03 -0600 |
| commit | 78940d5b7ee2f3e5020f87b400db1785b37a7d71 (patch) | |
| tree | 5784212a5a219f9c648b2e3afc6c952ea1a2da46 /packages/ui/src/components/code.tsx | |
| parent | b84a1f714bf0b81efdf89a0dd6e35fa2b3e8692a (diff) | |
| download | opencode-78940d5b7ee2f3e5020f87b400db1785b37a7d71.tar.gz opencode-78940d5b7ee2f3e5020f87b400db1785b37a7d71.zip | |
wip(app): file context
Diffstat (limited to 'packages/ui/src/components/code.tsx')
| -rw-r--r-- | packages/ui/src/components/code.tsx | 105 |
1 files changed, 102 insertions, 3 deletions
diff --git a/packages/ui/src/components/code.tsx b/packages/ui/src/components/code.tsx index fda08260f..ed7db368c 100644 --- a/packages/ui/src/components/code.tsx +++ b/packages/ui/src/components/code.tsx @@ -1,18 +1,52 @@ -import { type FileContents, File, FileOptions, LineAnnotation } from "@pierre/diffs" -import { ComponentProps, createEffect, createMemo, splitProps } from "solid-js" +import { type FileContents, File, FileOptions, LineAnnotation, type SelectedLineRange } from "@pierre/diffs" +import { ComponentProps, createEffect, createMemo, onCleanup, splitProps } from "solid-js" import { createDefaultOptions, styleVariables } from "../pierre" import { getWorkerPool } from "../pierre/worker" +type SelectionSide = "additions" | "deletions" + export type CodeProps<T = {}> = FileOptions<T> & { file: FileContents annotations?: LineAnnotation<T>[] + selectedLines?: SelectedLineRange | null class?: string classList?: ComponentProps<"div">["classList"] } +function findElement(node: Node | null): HTMLElement | undefined { + if (!node) return + if (node instanceof HTMLElement) return node + return node.parentElement ?? undefined +} + +function findLineNumber(node: Node | null): number | undefined { + const element = findElement(node) + if (!element) return + + const line = element.closest("[data-line]") + if (!(line instanceof HTMLElement)) return + + const value = parseInt(line.dataset.line ?? "", 10) + if (Number.isNaN(value)) return + + return value +} + +function findSide(node: Node | null): SelectionSide | undefined { + const element = findElement(node) + if (!element) return + + const code = element.closest("[data-code]") + if (!(code instanceof HTMLElement)) return + + if (code.hasAttribute("data-deletions")) return "deletions" + return "additions" +} + export function Code<T>(props: CodeProps<T>) { let container!: HTMLDivElement - const [local, others] = splitProps(props, ["file", "class", "classList", "annotations"]) + + const [local, others] = splitProps(props, ["file", "class", "classList", "annotations", "selectedLines"]) const file = createMemo( () => @@ -25,6 +59,57 @@ export function Code<T>(props: CodeProps<T>) { ), ) + const getRoot = () => { + const host = container.querySelector("diffs-container") + if (!(host instanceof HTMLElement)) return + + const root = host.shadowRoot + if (!root) return + + return root + } + + const handleMouseUp = () => { + if (props.enableLineSelection !== true) return + + const root = getRoot() + if (!root) return + + const selection = 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 start = findLineNumber(anchor) + const end = findLineNumber(focus) + if (start === undefined || end === undefined) return + + const startSide = findSide(anchor) + const endSide = findSide(focus) + const side = startSide ?? endSide + + const range: SelectedLineRange = { + start, + end, + } + + if (side) range.side = side + if (endSide && side && endSide !== side) range.endSide = endSide + + file().setSelectedLines(range) + } + + createEffect(() => { + const current = file() + + onCleanup(() => { + current.cleanUp() + }) + }) + createEffect(() => { container.innerHTML = "" file().render({ @@ -34,6 +119,20 @@ export function Code<T>(props: CodeProps<T>) { }) }) + createEffect(() => { + file().setSelectedLines(local.selectedLines ?? null) + }) + + createEffect(() => { + if (props.enableLineSelection !== true) return + + container.addEventListener("mouseup", handleMouseUp) + + onCleanup(() => { + container.removeEventListener("mouseup", handleMouseUp) + }) + }) + return ( <div data-component="code" |
