From 0ce0cacb282c47943348a2af21ea00e721bcb9d9 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Wed, 21 Jan 2026 05:27:52 -0600 Subject: wip(app): line selection --- packages/app/src/components/prompt-input.tsx | 137 +++++++++++++++++---------- packages/app/src/context/prompt.tsx | 9 +- packages/app/src/pages/session.tsx | 34 ++++++- 3 files changed, 130 insertions(+), 50 deletions(-) (limited to 'packages/app/src') diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index 0d6a7641a..5e936737a 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -164,6 +164,18 @@ export const PromptInput: Component = (props) => { return files.pathFromTab(tab) }) + const selectionPreview = (path: string, selection?: FileSelection, preview?: string) => { + if (preview) return preview + if (!selection) return undefined + const content = files.get(path)?.content?.content + if (!content) return undefined + const start = Math.max(1, Math.min(selection.startLine, selection.endLine)) + const end = Math.max(selection.startLine, selection.endLine) + const lines = content.split("\n").slice(start - 1, end) + if (lines.length === 0) return undefined + return lines.slice(0, 2).join("\n") + } + const activeFileSelection = createMemo(() => { const path = activeFile() if (!path) return @@ -171,6 +183,11 @@ export const PromptInput: Component = (props) => { if (!range) return return selectionFromLines(range) }) + const activeSelectionPreview = createMemo(() => { + const path = activeFile() + if (!path) return + return selectionPreview(path, activeFileSelection()) + }) const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined)) const status = createMemo( () => @@ -1485,40 +1502,49 @@ export const PromptInput: Component = (props) => { 0 || !!activeFile()}> -
+
{(path) => ( -
- -
- {getDirectory(path())} - {getFilename(path())} - - {(sel) => ( - - {sel().startLine === sel().endLine - ? `:${sel().startLine}` - : `:${sel().startLine}-${sel().endLine}`} - - )} - - {language.t("prompt.context.active")} +
+
+ +
+ {getDirectory(path())} + {getFilename(path())} + + {(sel) => ( + + {sel().startLine === sel().endLine + ? `:${sel().startLine}` + : `:${sel().startLine}-${sel().endLine}`} + + )} + + {language.t("prompt.context.active")} +
+ prompt.context.removeActive()} + aria-label={language.t("prompt.context.removeActiveFile")} + />
- prompt.context.removeActive()} - aria-label={language.t("prompt.context.removeActiveFile")} - /> + + {(preview) => ( +
+                        {preview()}
+                      
+ )} +
)} - {(item) => ( -
- -
- {getDirectory(item.path)} - {getFilename(item.path)} - - {(sel) => ( - - {sel().startLine === sel().endLine - ? `:${sel().startLine}` - : `:${sel().startLine}-${sel().endLine}`} - + {(item) => { + const preview = createMemo(() => selectionPreview(item.path, item.selection, item.preview)) + return ( +
+
+ +
+ {getDirectory(item.path)} + {getFilename(item.path)} + + {(sel) => ( + + {sel().startLine === sel().endLine + ? `:${sel().startLine}` + : `:${sel().startLine}-${sel().endLine}`} + + )} + +
+ prompt.context.remove(item.key)} + aria-label={language.t("prompt.context.removeFile")} + /> +
+ + {(comment) =>
{comment()}
} +
+ + {(content) => ( +
+                          {content()}
+                        
)}
- prompt.context.remove(item.key)} - aria-label={language.t("prompt.context.removeFile")} - /> -
- )} + ) + }}
diff --git a/packages/app/src/context/prompt.tsx b/packages/app/src/context/prompt.tsx index 993d7e7a8..a76d9d5f1 100644 --- a/packages/app/src/context/prompt.tsx +++ b/packages/app/src/context/prompt.tsx @@ -4,6 +4,7 @@ import { batch, createMemo, createRoot, onCleanup } from "solid-js" import { useParams } from "@solidjs/router" import type { FileSelection } from "@/context/file" import { Persist, persisted } from "@/utils/persist" +import { checksum } from "@opencode-ai/util/encode" interface PartBase { content: string @@ -41,6 +42,8 @@ export type FileContextItem = { type: "file" path: string selection?: FileSelection + comment?: string + preview?: string } export type ContextItem = FileContextItem @@ -135,7 +138,11 @@ function createPromptSession(dir: string, id: string | undefined) { if (item.type !== "file") return item.type const start = item.selection?.startLine const end = item.selection?.endLine - return `${item.type}:${item.path}:${start}:${end}` + const key = `${item.type}:${item.path}:${start}:${end}` + const comment = item.comment?.trim() + if (!comment) return key + const digest = checksum(comment) ?? comment + return `${key}:c=${digest.slice(0, 8)}` } return { diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index ad6d360dc..1e0d7a89e 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -81,6 +81,7 @@ interface SessionReviewTabProps { diffStyle: DiffStyle onDiffStyleChange?: (style: DiffStyle) => void onViewFile?: (file: string) => void + onLineComment?: (comment: { file: string; selection: SelectedLineRange; comment: string; preview?: string }) => void classes?: { root?: string header?: string @@ -166,6 +167,7 @@ function SessionReviewTab(props: SessionReviewTabProps) { onDiffStyleChange={props.onDiffStyleChange} onViewFile={props.onViewFile} readFile={readFile} + onLineComment={props.onLineComment} /> ) } @@ -488,8 +490,36 @@ export default function Page() { setStore("expanded", id, status().type !== "idle") }) + const selectionPreview = (path: string, selection: FileSelection) => { + const content = file.get(path)?.content?.content + if (!content) return undefined + const start = Math.max(1, Math.min(selection.startLine, selection.endLine)) + const end = Math.max(selection.startLine, selection.endLine) + const lines = content.split("\n").slice(start - 1, end) + if (lines.length === 0) return undefined + return lines.slice(0, 2).join("\n") + } + const addSelectionToContext = (path: string, selection: FileSelection) => { - prompt.context.add({ type: "file", path, selection }) + const preview = selectionPreview(path, selection) + prompt.context.add({ type: "file", path, selection, preview }) + } + + const addCommentToContext = (input: { + file: string + selection: SelectedLineRange + comment: string + preview?: string + }) => { + const selection = selectionFromLines(input.selection) + const preview = input.preview ?? selectionPreview(input.file, selection) + prompt.context.add({ + type: "file", + path: input.file, + selection, + comment: input.comment, + preview, + }) } command.register(() => [ @@ -1402,6 +1432,7 @@ export default function Page() { diffs={diffs} view={view} diffStyle="unified" + onLineComment={addCommentToContext} onViewFile={(path) => { const value = file.tab(path) tabs().open(value) @@ -1717,6 +1748,7 @@ export default function Page() { view={view} diffStyle={layout.review.diffStyle()} onDiffStyleChange={layout.review.setDiffStyle} + onLineComment={addCommentToContext} onViewFile={(path) => { const value = file.tab(path) tabs().open(value) -- cgit v1.2.3