diff options
| author | Adam <[email protected]> | 2025-09-26 11:41:15 -0500 |
|---|---|---|
| committer | Adam <[email protected]> | 2025-10-02 08:34:01 -0500 |
| commit | cc955098cd8714bcf1cc91e6a4a6625e38710b05 (patch) | |
| tree | 49adeef653a6705c37b2e06cde1b5066765eeed3 /packages/app/src/components/code.tsx | |
| parent | 8699e896e604762d45df7d4e1b3433e69575e9ab (diff) | |
| download | opencode-cc955098cd8714bcf1cc91e6a4a6625e38710b05.tar.gz opencode-cc955098cd8714bcf1cc91e6a4a6625e38710b05.zip | |
wip: desktop work
Diffstat (limited to 'packages/app/src/components/code.tsx')
| -rw-r--r-- | packages/app/src/components/code.tsx | 73 |
1 files changed, 64 insertions, 9 deletions
diff --git a/packages/app/src/components/code.tsx b/packages/app/src/components/code.tsx index 63f527c46..40a40aa9a 100644 --- a/packages/app/src/components/code.tsx +++ b/packages/app/src/components/code.tsx @@ -1,8 +1,11 @@ import { bundledLanguages, type BundledLanguage, type ShikiTransformer } from "shiki" import { splitProps, type ComponentProps, createEffect, onMount, onCleanup, createMemo, createResource } from "solid-js" import { useLocal, useShiki } from "@/context" +import type { TextSelection } from "@/context/local" import { getFileExtension, getNodeOffsetInLine, getSelectionInContainer } from "@/utils" +type DefinedSelection = Exclude<TextSelection, undefined> + interface Props extends ComponentProps<"div"> { code: string path: string @@ -21,17 +24,66 @@ export function Code(props: Props) { let container: HTMLDivElement | undefined let isProgrammaticSelection = false - const [html] = createResource(async () => { - if (!highlighter.getLoadedLanguages().includes(lang())) { - await highlighter.loadLanguage(lang() as BundledLanguage) + const ranges = createMemo<DefinedSelection[]>(() => { + const items = ctx.context.all() as Array<{ type: "file"; path: string; selection?: DefinedSelection }> + const result: DefinedSelection[] = [] + for (const item of items) { + if (item.path !== local.path) continue + const selection = item.selection + if (!selection) continue + result.push(selection) } - return highlighter.codeToHtml(local.code || "", { - lang: lang() && lang() in bundledLanguages ? lang() : "text", - theme: "opencode", - transformers: [transformerUnifiedDiff(), transformerDiffGroups()], - }) as string + return result }) + const createLineNumberTransformer = (selections: DefinedSelection[]): ShikiTransformer => { + const highlighted = new Set<number>() + for (const selection of selections) { + const startLine = selection.startLine + const endLine = selection.endLine + const start = Math.max(1, Math.min(startLine, endLine)) + const end = Math.max(start, Math.max(startLine, endLine)) + const count = end - start + 1 + if (count <= 0) continue + const values = Array.from({ length: count }, (_, index) => start + index) + for (const value of values) highlighted.add(value) + } + return { + name: "line-number-highlight", + line(node, index) { + if (!highlighted.has(index)) return + this.addClassToHast(node, "line-number-highlight") + const children = node.children + if (!Array.isArray(children)) return + for (const child of children) { + if (!child || typeof child !== "object") continue + const element = child as { type?: string; properties?: { className?: string[] } } + if (element.type !== "element") continue + const className = element.properties?.className + if (!Array.isArray(className)) continue + const matches = className.includes("diff-oldln") || className.includes("diff-newln") + if (!matches) continue + if (className.includes("line-number-highlight")) continue + className.push("line-number-highlight") + } + }, + } + } + + const [html] = createResource( + () => ranges(), + async (activeRanges) => { + if (!highlighter.getLoadedLanguages().includes(lang())) { + await highlighter.loadLanguage(lang() as BundledLanguage) + } + return highlighter.codeToHtml(local.code || "", { + lang: lang() && lang() in bundledLanguages ? lang() : "text", + theme: "opencode", + transformers: [transformerUnifiedDiff(), transformerDiffGroups(), createLineNumberTransformer(activeRanges)], + }) as string + }, + ) + onMount(() => { if (!container) return @@ -283,7 +335,7 @@ export function Code(props: Props) { [&]:[counter-reset:line] [&_pre]:focus-visible:outline-none [&_pre]:overflow-x-auto [&_pre]:no-scrollbar - [&_code]:min-w-full [&_code]:inline-block [&_code]:pb-40 + [&_code]:min-w-full [&_code]:inline-block [&_.tab]:relative [&_.tab::before]:content['⇥'] [&_.tab::before]:absolute @@ -303,6 +355,9 @@ export function Code(props: Props) { [&_.line::before]:select-none [&_.line::before]:[counter-increment:line] [&_.line::before]:content-[counter(line)] + [&_.line-number-highlight]:bg-accent/20 + [&_.line-number-highlight::before]:bg-accent/40! + [&_.line-number-highlight::before]:text-background-panel! [&_code.code-diff_.line::before]:content-[''] [&_code.code-diff_.line::before]:w-0 [&_code.code-diff_.line::before]:pr-0 |
