diff options
| author | Adam <[email protected]> | 2026-01-21 06:17:55 -0600 |
|---|---|---|
| committer | Adam <[email protected]> | 2026-01-22 22:12:12 -0600 |
| commit | cb481d9ac861813d4ff091ed33bcac9e882da1a1 (patch) | |
| tree | c08be4b96815b74ac6dc1e3bab6359cd5dbb27b3 /packages/app/src/context/comments.tsx | |
| parent | 0ce0cacb282c47943348a2af21ea00e721bcb9d9 (diff) | |
| download | opencode-cb481d9ac861813d4ff091ed33bcac9e882da1a1.tar.gz opencode-cb481d9ac861813d4ff091ed33bcac9e882da1a1.zip | |
wip(app): line selection
Diffstat (limited to 'packages/app/src/context/comments.tsx')
| -rw-r--r-- | packages/app/src/context/comments.tsx | 140 |
1 files changed, 140 insertions, 0 deletions
diff --git a/packages/app/src/context/comments.tsx b/packages/app/src/context/comments.tsx new file mode 100644 index 000000000..12ee977e9 --- /dev/null +++ b/packages/app/src/context/comments.tsx @@ -0,0 +1,140 @@ +import { batch, createMemo, createRoot, createSignal, onCleanup } from "solid-js" +import { createStore } from "solid-js/store" +import { createSimpleContext } from "@opencode-ai/ui/context" +import { useParams } from "@solidjs/router" +import { Persist, persisted } from "@/utils/persist" +import type { SelectedLineRange } from "@/context/file" + +export type LineComment = { + id: string + file: string + selection: SelectedLineRange + comment: string + time: number +} + +type CommentFocus = { file: string; id: string } + +const WORKSPACE_KEY = "__workspace__" +const MAX_COMMENT_SESSIONS = 20 + +type CommentSession = ReturnType<typeof createCommentSession> + +type CommentCacheEntry = { + value: CommentSession + dispose: VoidFunction +} + +function createCommentSession(dir: string, id: string | undefined) { + const legacy = `${dir}/comments${id ? "/" + id : ""}.v1` + + const [store, setStore, _, ready] = persisted( + Persist.scoped(dir, id, "comments", [legacy]), + createStore<{ + comments: Record<string, LineComment[]> + }>({ + comments: {}, + }), + ) + + const [focus, setFocus] = createSignal<CommentFocus | null>(null) + + const list = (file: string) => store.comments[file] ?? [] + + const add = (input: Omit<LineComment, "id" | "time">) => { + const next: LineComment = { + id: crypto.randomUUID(), + time: Date.now(), + ...input, + } + + batch(() => { + setStore("comments", input.file, (items) => [...(items ?? []), next]) + setFocus({ file: input.file, id: next.id }) + }) + + return next + } + + const remove = (file: string, id: string) => { + setStore("comments", file, (items) => (items ?? []).filter((x) => x.id !== id)) + setFocus((current) => (current?.id === id ? null : current)) + } + + const all = createMemo(() => { + const files = Object.keys(store.comments) + const items = files.flatMap((file) => store.comments[file] ?? []) + return items.slice().sort((a, b) => a.time - b.time) + }) + + return { + ready, + list, + all, + add, + remove, + focus: createMemo(() => focus()), + setFocus, + clearFocus: () => setFocus(null), + } +} + +export const { use: useComments, provider: CommentsProvider } = createSimpleContext({ + name: "Comments", + gate: false, + init: () => { + const params = useParams() + const cache = new Map<string, CommentCacheEntry>() + + const disposeAll = () => { + for (const entry of cache.values()) { + entry.dispose() + } + cache.clear() + } + + onCleanup(disposeAll) + + const prune = () => { + while (cache.size > MAX_COMMENT_SESSIONS) { + const first = cache.keys().next().value + if (!first) return + const entry = cache.get(first) + entry?.dispose() + cache.delete(first) + } + } + + const load = (dir: string, id: string | undefined) => { + const key = `${dir}:${id ?? WORKSPACE_KEY}` + const existing = cache.get(key) + if (existing) { + cache.delete(key) + cache.set(key, existing) + return existing.value + } + + const entry = createRoot((dispose) => ({ + value: createCommentSession(dir, id), + dispose, + })) + + cache.set(key, entry) + prune() + return entry.value + } + + const session = createMemo(() => load(params.dir!, params.id)) + + return { + ready: () => session().ready(), + list: (file: string) => session().list(file), + all: () => session().all(), + add: (input: Omit<LineComment, "id" | "time">) => session().add(input), + remove: (file: string, id: string) => session().remove(file, id), + focus: () => session().focus(), + setFocus: (focus: CommentFocus | null) => session().setFocus(focus), + clearFocus: () => session().clearFocus(), + } + }, +}) |
