diff options
| author | Adam <[email protected]> | 2026-01-04 15:40:25 -0600 |
|---|---|---|
| committer | Adam <[email protected]> | 2026-01-22 22:12:12 -0600 |
| commit | 640d1f1ecc7a2b46fb2bafed760c7348c70579a8 (patch) | |
| tree | 090f22b0e98053e7089133f164b17cff0367daa6 /packages/app/src/components | |
| parent | 2e53697da01d1417845567296774166350e786f1 (diff) | |
| download | opencode-640d1f1ecc7a2b46fb2bafed760c7348c70579a8.tar.gz opencode-640d1f1ecc7a2b46fb2bafed760c7348c70579a8.zip | |
wip(app): line selection
Diffstat (limited to 'packages/app/src/components')
| -rw-r--r-- | packages/app/src/components/prompt-input.tsx | 43 | ||||
| -rw-r--r-- | packages/app/src/components/session/session-context-tab.tsx | 12 |
2 files changed, 34 insertions, 21 deletions
diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index 44a1db253..0d6a7641a 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -15,7 +15,7 @@ import { import { createStore, produce } from "solid-js/store" import { createFocusSignal } from "@solid-primitives/active-element" import { useLocal } from "@/context/local" -import { useFile, type FileSelection } from "@/context/file" +import { selectionFromLines, useFile, type FileSelection } from "@/context/file" import { ContentPart, DEFAULT_PROMPT, @@ -163,6 +163,14 @@ export const PromptInput: Component<PromptInputProps> = (props) => { if (!tab) return return files.pathFromTab(tab) }) + + const activeFileSelection = createMemo(() => { + const path = activeFile() + if (!path) return + const range = files.selectedLines(path) + if (!range) return + return selectionFromLines(range) + }) const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined)) const status = createMemo( () => @@ -1256,7 +1264,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => { const activePath = activeFile() if (activePath && prompt.context.activeTab()) { - addContextFile(activePath) + addContextFile(activePath, activeFileSelection()) } for (const item of prompt.context.items()) { @@ -1476,22 +1484,31 @@ export const PromptInput: Component<PromptInputProps> = (props) => { </div> </div> </Show> - <Show when={false && (prompt.context.items().length > 0 || !!activeFile())}> - <div class="flex flex-wrap items-center gap-2 px-3 pt-3"> + <Show when={prompt.context.items().length > 0 || !!activeFile()}> + <div class="flex flex-wrap items-center gap-1.5 px-3 pt-3"> <Show when={prompt.context.activeTab() ? activeFile() : undefined}> {(path) => ( - <div class="flex items-center gap-2 px-2 py-1 rounded-md bg-surface-base border border-border-base max-w-full"> - <FileIcon node={{ path: path(), type: "file" }} class="shrink-0 size-4" /> - <div class="flex items-center text-12-regular min-w-0"> + <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> <IconButton type="button" icon="close" variant="ghost" - class="h-6 w-6" + class="h-5 w-5" onClick={() => prompt.context.removeActive()} aria-label={language.t("prompt.context.removeActiveFile")} /> @@ -1501,7 +1518,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => { <Show when={!prompt.context.activeTab() && !!activeFile()}> <button type="button" - class="flex items-center gap-2 px-2 py-1 rounded-md bg-surface-base border border-border-base text-12-regular text-text-weak hover:bg-surface-raised-base-hover" + 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" onClick={() => prompt.context.addActive()} > <Icon name="plus-small" size="small" /> @@ -1510,9 +1527,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => { </Show> <For each={prompt.context.items()}> {(item) => ( - <div class="flex items-center gap-2 px-2 py-1 rounded-md bg-surface-base border border-border-base max-w-full"> - <FileIcon node={{ path: item.path, type: "file" }} class="shrink-0 size-4" /> - <div class="flex items-center text-12-regular min-w-0"> + <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}> @@ -1529,7 +1546,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => { type="button" icon="close" variant="ghost" - class="h-6 w-6" + class="h-5 w-5" onClick={() => prompt.context.remove(item.key)} aria-label={language.t("prompt.context.removeFile")} /> diff --git a/packages/app/src/components/session/session-context-tab.tsx b/packages/app/src/components/session/session-context-tab.tsx index b41578910..57648c380 100644 --- a/packages/app/src/components/session/session-context-tab.tsx +++ b/packages/app/src/components/session/session-context-tab.tsx @@ -282,7 +282,9 @@ export function SessionContextTab(props: SessionContextTabProps) { } }) - return <Code file={file()} overflow="wrap" class="select-text" /> + return ( + <Code file={file()} overflow="wrap" class="select-text" onRendered={() => requestAnimationFrame(restoreScroll)} /> + ) } function RawMessage(msgProps: { message: Message }) { @@ -314,19 +316,13 @@ export function SessionContextTab(props: SessionContextTabProps) { let frame: number | undefined let pending: { x: number; y: number } | undefined - const restoreScroll = (retries = 0) => { + const restoreScroll = () => { const el = scroll if (!el) return const s = props.view()?.scroll("context") if (!s) return - // Wait for content to be scrollable - content may not have rendered yet - if (el.scrollHeight <= el.clientHeight && retries < 10) { - requestAnimationFrame(() => restoreScroll(retries + 1)) - return - } - if (el.scrollTop !== s.y) el.scrollTop = s.y if (el.scrollLeft !== s.x) el.scrollLeft = s.x } |
