diff options
| author | Adam <[email protected]> | 2026-01-21 05:27:52 -0600 |
|---|---|---|
| committer | Adam <[email protected]> | 2026-01-22 22:12:12 -0600 |
| commit | 0ce0cacb282c47943348a2af21ea00e721bcb9d9 (patch) | |
| tree | e1c17ec3dc03ce1fd86f348059a6401e700eb60d /packages/app/src/components | |
| parent | 640d1f1ecc7a2b46fb2bafed760c7348c70579a8 (diff) | |
| download | opencode-0ce0cacb282c47943348a2af21ea00e721bcb9d9.tar.gz opencode-0ce0cacb282c47943348a2af21ea00e721bcb9d9.zip | |
wip(app): line selection
Diffstat (limited to 'packages/app/src/components')
| -rw-r--r-- | packages/app/src/components/prompt-input.tsx | 137 |
1 files changed, 89 insertions, 48 deletions
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<PromptInputProps> = (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<PromptInputProps> = (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<PromptInputProps> = (props) => { </div> </Show> <Show when={prompt.context.items().length > 0 || !!activeFile()}> - <div class="flex flex-wrap items-center gap-1.5 px-3 pt-3"> + <div class="flex flex-nowrap items-start gap-1.5 px-3 pt-3 overflow-x-auto no-scrollbar"> <Show when={prompt.context.activeTab() ? activeFile() : undefined}> {(path) => ( - <div class="flex items-center gap-1.5 px-1.5 py-0.5 rounded-md bg-surface-base border border-border-base max-w-full"> - <FileIcon node={{ path: path(), type: "file" }} class="shrink-0 size-3.5" /> - <div class="flex items-center text-11-regular min-w-0"> - <span class="text-text-weak whitespace-nowrap truncate min-w-0">{getDirectory(path())}</span> - <span class="text-text-strong whitespace-nowrap">{getFilename(path())}</span> - <Show when={activeFileSelection()}> - {(sel) => ( - <span class="text-text-weak whitespace-nowrap ml-1"> - {sel().startLine === sel().endLine - ? `:${sel().startLine}` - : `:${sel().startLine}-${sel().endLine}`} - </span> - )} - </Show> - <span class="text-text-weak whitespace-nowrap ml-1">{language.t("prompt.context.active")}</span> + <div class="shrink-0 flex flex-col gap-1 rounded-md bg-surface-base border border-border-base px-2 py-1 max-w-[320px]"> + <div class="flex items-center gap-1.5"> + <FileIcon node={{ path: path(), type: "file" }} class="shrink-0 size-3.5" /> + <div class="flex items-center text-11-regular min-w-0"> + <span class="text-text-weak whitespace-nowrap truncate min-w-0">{getDirectory(path())}</span> + <span class="text-text-strong whitespace-nowrap">{getFilename(path())}</span> + <Show when={activeFileSelection()}> + {(sel) => ( + <span class="text-text-weak whitespace-nowrap ml-1"> + {sel().startLine === sel().endLine + ? `:${sel().startLine}` + : `:${sel().startLine}-${sel().endLine}`} + </span> + )} + </Show> + <span class="text-text-weak whitespace-nowrap ml-1">{language.t("prompt.context.active")}</span> + </div> + <IconButton + type="button" + icon="close" + variant="ghost" + class="h-5 w-5" + onClick={() => prompt.context.removeActive()} + aria-label={language.t("prompt.context.removeActiveFile")} + /> </div> - <IconButton - type="button" - icon="close" - variant="ghost" - class="h-5 w-5" - onClick={() => prompt.context.removeActive()} - aria-label={language.t("prompt.context.removeActiveFile")} - /> + <Show when={activeSelectionPreview()}> + {(preview) => ( + <pre class="text-10-regular text-text-weak font-mono whitespace-pre-wrap leading-4"> + {preview()} + </pre> + )} + </Show> </div> )} </Show> <Show when={!prompt.context.activeTab() && !!activeFile()}> <button type="button" - class="flex items-center gap-1.5 px-1.5 py-0.5 rounded-md bg-surface-base border border-border-base text-11-regular text-text-weak hover:bg-surface-raised-base-hover" + class="shrink-0 flex items-center gap-1.5 px-1.5 py-0.5 rounded-md bg-surface-base border border-border-base text-11-regular text-text-weak hover:bg-surface-raised-base-hover" onClick={() => prompt.context.addActive()} > <Icon name="plus-small" size="small" /> @@ -1526,32 +1552,47 @@ export const PromptInput: Component<PromptInputProps> = (props) => { </button> </Show> <For each={prompt.context.items()}> - {(item) => ( - <div class="flex items-center gap-1.5 px-1.5 py-0.5 rounded-md bg-surface-base border border-border-base max-w-full"> - <FileIcon node={{ path: item.path, type: "file" }} class="shrink-0 size-3.5" /> - <div class="flex items-center text-11-regular min-w-0"> - <span class="text-text-weak whitespace-nowrap truncate min-w-0">{getDirectory(item.path)}</span> - <span class="text-text-strong whitespace-nowrap">{getFilename(item.path)}</span> - <Show when={item.selection}> - {(sel) => ( - <span class="text-text-weak whitespace-nowrap ml-1"> - {sel().startLine === sel().endLine - ? `:${sel().startLine}` - : `:${sel().startLine}-${sel().endLine}`} - </span> + {(item) => { + const preview = createMemo(() => selectionPreview(item.path, item.selection, item.preview)) + return ( + <div class="shrink-0 flex flex-col gap-1 rounded-md bg-surface-base border border-border-base px-2 py-1 max-w-[320px]"> + <div class="flex items-center gap-1.5"> + <FileIcon node={{ path: item.path, type: "file" }} class="shrink-0 size-3.5" /> + <div class="flex items-center text-11-regular min-w-0"> + <span class="text-text-weak whitespace-nowrap truncate min-w-0">{getDirectory(item.path)}</span> + <span class="text-text-strong whitespace-nowrap">{getFilename(item.path)}</span> + <Show when={item.selection}> + {(sel) => ( + <span class="text-text-weak whitespace-nowrap ml-1"> + {sel().startLine === sel().endLine + ? `:${sel().startLine}` + : `:${sel().startLine}-${sel().endLine}`} + </span> + )} + </Show> + </div> + <IconButton + type="button" + icon="close" + variant="ghost" + class="h-5 w-5" + onClick={() => prompt.context.remove(item.key)} + aria-label={language.t("prompt.context.removeFile")} + /> + </div> + <Show when={item.comment}> + {(comment) => <div class="text-11-regular text-text-strong">{comment()}</div>} + </Show> + <Show when={preview()}> + {(content) => ( + <pre class="text-10-regular text-text-weak font-mono whitespace-pre-wrap leading-4"> + {content()} + </pre> )} </Show> </div> - <IconButton - type="button" - icon="close" - variant="ghost" - class="h-5 w-5" - onClick={() => prompt.context.remove(item.key)} - aria-label={language.t("prompt.context.removeFile")} - /> - </div> - )} + ) + }} </For> </div> </Show> |
