summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authoradamelmore <[email protected]>2026-01-25 11:25:26 -0600
committeradamelmore <[email protected]>2026-01-25 11:25:31 -0600
commit9a89cd91d71987b53964f2a296cf3b9d4b88aa23 (patch)
tree6cb3447bf53c4879e74909b3495b9e818f793cfd
parent65ac3182828c6fcb1a3feb7b7d71ea197b49e6af (diff)
downloadopencode-9a89cd91d71987b53964f2a296cf3b9d4b88aa23.tar.gz
opencode-9a89cd91d71987b53964f2a296cf3b9d4b88aa23.zip
fix(app): line selection styling
-rw-r--r--packages/ui/src/components/diff-ssr.tsx84
-rw-r--r--packages/ui/src/components/diff.tsx73
-rw-r--r--packages/ui/src/pierre/index.ts10
3 files changed, 134 insertions, 33 deletions
diff --git a/packages/ui/src/components/diff-ssr.tsx b/packages/ui/src/components/diff-ssr.tsx
index ac98a6d24..9456e4a3c 100644
--- a/packages/ui/src/components/diff-ssr.tsx
+++ b/packages/ui/src/components/diff-ssr.tsx
@@ -29,10 +29,16 @@ export function Diff<T>(props: SSRDiffProps<T>) {
const getRoot = () => fileDiffRef?.shadowRoot ?? undefined
const findSide = (element: HTMLElement): "additions" | "deletions" => {
+ const line = element.closest("[data-line], [data-alt-line]")
+ if (line instanceof HTMLElement) {
+ const type = line.dataset.lineType
+ if (type === "change-deletion") return "deletions"
+ if (type === "change-addition" || type === "change-additions") return "additions"
+ }
+
const code = element.closest("[data-code]")
if (!(code instanceof HTMLElement)) return "additions"
- if (code.hasAttribute("data-deletions")) return "deletions"
- return "additions"
+ return code.hasAttribute("data-deletions") ? "deletions" : "additions"
}
const applyCommentedLines = (ranges: SelectedLineRange[]) => {
@@ -45,19 +51,69 @@ export function Diff<T>(props: SSRDiffProps<T>) {
node.removeAttribute("data-comment-selected")
}
+ const diffs = root.querySelector("[data-diffs]")
+ if (!(diffs instanceof HTMLElement)) return
+
+ const split = diffs.dataset.type === "split"
+
+ const code = Array.from(diffs.querySelectorAll("[data-code]")).filter(
+ (node): node is HTMLElement => node instanceof HTMLElement,
+ )
+ if (code.length === 0) return
+
+ const lineIndex = (element: HTMLElement) => {
+ const raw = element.dataset.lineIndex
+ if (!raw) return
+ const values = raw
+ .split(",")
+ .map((value) => parseInt(value, 10))
+ .filter((value) => !Number.isNaN(value))
+ if (values.length === 0) return
+ if (!split) return values[0]
+ if (values.length === 2) return values[1]
+ return values[0]
+ }
+
+ const rowIndex = (line: number, side: "additions" | "deletions" | undefined) => {
+ const nodes = Array.from(root.querySelectorAll(`[data-line="${line}"], [data-alt-line="${line}"]`)).filter(
+ (node): node is HTMLElement => node instanceof HTMLElement,
+ )
+ if (nodes.length === 0) return
+
+ const targetSide = side ?? "additions"
+
+ for (const node of nodes) {
+ if (findSide(node) === targetSide) return lineIndex(node)
+ if (parseInt(node.dataset.altLine ?? "", 10) === line) return lineIndex(node)
+ }
+ }
+
for (const range of ranges) {
- const start = Math.max(1, Math.min(range.start, range.end))
- const end = Math.max(range.start, range.end)
-
- for (let line = start; line <= end; line++) {
- const expectedSide =
- line === end ? (range.endSide ?? range.side) : line === start ? range.side : (range.side ?? range.endSide)
-
- const nodes = Array.from(root.querySelectorAll(`[data-line="${line}"], [data-alt-line="${line}"]`))
- for (const node of nodes) {
- if (!(node instanceof HTMLElement)) continue
- if (expectedSide && findSide(node) !== expectedSide) continue
- node.setAttribute("data-comment-selected", "")
+ const start = rowIndex(range.start, range.side)
+ if (start === undefined) continue
+
+ const end = (() => {
+ const same = range.end === range.start && (range.endSide == null || range.endSide === range.side)
+ if (same) return start
+ return rowIndex(range.end, range.endSide ?? range.side)
+ })()
+ if (end === undefined) continue
+
+ const first = Math.min(start, end)
+ const last = Math.max(start, end)
+
+ for (const block of code) {
+ for (const element of Array.from(block.children)) {
+ if (!(element instanceof HTMLElement)) continue
+ const idx = lineIndex(element)
+ if (idx === undefined) continue
+ if (idx > last) break
+ if (idx < first) continue
+ element.setAttribute("data-comment-selected", "")
+ const next = element.nextSibling
+ if (next instanceof HTMLElement && next.hasAttribute("data-line-annotation")) {
+ next.setAttribute("data-comment-selected", "")
+ }
}
}
}
diff --git a/packages/ui/src/components/diff.tsx b/packages/ui/src/components/diff.tsx
index 5aff93d1d..2fb2ea3bc 100644
--- a/packages/ui/src/components/diff.tsx
+++ b/packages/ui/src/components/diff.tsx
@@ -191,24 +191,69 @@ export function Diff<T>(props: DiffProps<T>) {
node.removeAttribute("data-comment-selected")
}
- for (const range of ranges) {
- const start = Math.max(1, Math.min(range.start, range.end))
- const end = Math.max(range.start, range.end)
+ const diffs = root.querySelector("[data-diffs]")
+ if (!(diffs instanceof HTMLElement)) return
+
+ const split = diffs.dataset.type === "split"
+
+ const code = Array.from(diffs.querySelectorAll("[data-code]")).filter(
+ (node): node is HTMLElement => node instanceof HTMLElement,
+ )
+ if (code.length === 0) return
+
+ const lineIndex = (element: HTMLElement) => {
+ const raw = element.dataset.lineIndex
+ if (!raw) return
+ const values = raw
+ .split(",")
+ .map((value) => parseInt(value, 10))
+ .filter((value) => !Number.isNaN(value))
+ if (values.length === 0) return
+ if (!split) return values[0]
+ if (values.length === 2) return values[1]
+ return values[0]
+ }
- for (let line = start; line <= end; line++) {
- const expectedSide =
- line === end ? (range.endSide ?? range.side) : line === start ? range.side : (range.side ?? range.endSide)
+ const rowIndex = (line: number, side: SelectionSide | undefined) => {
+ const nodes = Array.from(root.querySelectorAll(`[data-line="${line}"], [data-alt-line="${line}"]`)).filter(
+ (node): node is HTMLElement => node instanceof HTMLElement,
+ )
+ if (nodes.length === 0) return
- const nodes = Array.from(root.querySelectorAll(`[data-line="${line}"], [data-alt-line="${line}"]`))
- for (const node of nodes) {
- if (!(node instanceof HTMLElement)) continue
+ const targetSide = side ?? "additions"
- if (expectedSide) {
- const side = findSide(node)
- if (side && side !== expectedSide) continue
- }
+ for (const node of nodes) {
+ if (findSide(node) === targetSide) return lineIndex(node)
+ if (parseInt(node.dataset.altLine ?? "", 10) === line) return lineIndex(node)
+ }
+ }
- node.setAttribute("data-comment-selected", "")
+ for (const range of ranges) {
+ const start = rowIndex(range.start, range.side)
+ if (start === undefined) continue
+
+ const end = (() => {
+ const same = range.end === range.start && (range.endSide == null || range.endSide === range.side)
+ if (same) return start
+ return rowIndex(range.end, range.endSide ?? range.side)
+ })()
+ if (end === undefined) continue
+
+ const first = Math.min(start, end)
+ const last = Math.max(start, end)
+
+ for (const block of code) {
+ for (const element of Array.from(block.children)) {
+ if (!(element instanceof HTMLElement)) continue
+ const idx = lineIndex(element)
+ if (idx === undefined) continue
+ if (idx > last) break
+ if (idx < first) continue
+ element.setAttribute("data-comment-selected", "")
+ const next = element.nextSibling
+ if (next instanceof HTMLElement && next.hasAttribute("data-line-annotation")) {
+ next.setAttribute("data-comment-selected", "")
+ }
}
}
}
diff --git a/packages/ui/src/pierre/index.ts b/packages/ui/src/pierre/index.ts
index 54262e4aa..a83af9890 100644
--- a/packages/ui/src/pierre/index.ts
+++ b/packages/ui/src/pierre/index.ts
@@ -44,12 +44,12 @@ const unsafeCSS = `
background-color: var(--diffs-bg-selection-text);
}
-[data-diffs] [data-comment-selected] {
- background-color: var(--diffs-bg-selection);
+[data-diffs] [data-comment-selected]:not([data-selected-line]) [data-column-content] {
+ box-shadow: inset 0 0 0 9999px var(--diffs-bg-selection);
}
-[data-diffs] [data-comment-selected] [data-column-number] {
- background-color: var(--diffs-bg-selection-number);
+[data-diffs] [data-comment-selected]:not([data-selected-line]) [data-column-number] {
+ box-shadow: inset 0 0 0 9999px var(--diffs-bg-selection-number);
color: var(--diffs-selection-number-fg);
}
@@ -73,7 +73,7 @@ const unsafeCSS = `
/* The deletion word-diff emphasis is stronger than additions; soften it while selected so the selection highlight reads consistently. */
[data-diffs] [data-line-type='change-deletion'][data-selected-line] {
--diffs-bg-deletion-emphasis: light-dark(
- rgb(from var(--diffs-deletion-base) r g b / 0.15),
+ rgb(from var(--diffs-deletion-base) r g b / 0.07),
rgb(from var(--diffs-deletion-base) r g b / 0.1)
);
}