summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src/components
diff options
context:
space:
mode:
authorAdam <[email protected]>2026-01-04 15:40:25 -0600
committerAdam <[email protected]>2026-01-22 22:12:12 -0600
commit640d1f1ecc7a2b46fb2bafed760c7348c70579a8 (patch)
tree090f22b0e98053e7089133f164b17cff0367daa6 /packages/app/src/components
parent2e53697da01d1417845567296774166350e786f1 (diff)
downloadopencode-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.tsx43
-rw-r--r--packages/app/src/components/session/session-context-tab.tsx12
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
}