summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAdam <[email protected]>2026-01-21 13:31:06 -0600
committerAdam <[email protected]>2026-01-22 22:12:12 -0600
commit1e1872aada10fc39126e13675560e692e80258d0 (patch)
tree0953f5260711ad2232af9f5d3ba5c3fe031f93ab
parentcb481d9ac861813d4ff091ed33bcac9e882da1a1 (diff)
downloadopencode-1e1872aada10fc39126e13675560e692e80258d0.tar.gz
opencode-1e1872aada10fc39126e13675560e692e80258d0.zip
wip(app): line selection
-rw-r--r--packages/app/src/components/prompt-input.tsx24
-rw-r--r--packages/app/src/pages/session.tsx3
-rw-r--r--packages/ui/src/components/markdown.css14
-rw-r--r--packages/ui/src/components/popover.tsx60
-rw-r--r--packages/ui/src/components/session-review.css19
-rw-r--r--packages/ui/src/components/session-review.tsx2
-rw-r--r--packages/ui/src/pierre/index.ts21
-rw-r--r--packages/ui/src/styles/base.css5
8 files changed, 83 insertions, 65 deletions
diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx
index b2c8cccca..10af351cb 100644
--- a/packages/app/src/components/prompt-input.tsx
+++ b/packages/app/src/components/prompt-input.tsx
@@ -167,18 +167,6 @@ 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
@@ -186,11 +174,6 @@ 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(
() =>
@@ -1534,13 +1517,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
aria-label={language.t("prompt.context.removeActiveFile")}
/>
</div>
- <Show when={activeSelectionPreview()}>
- {(preview) => (
- <pre class="text-10-regular text-text-weak font-mono whitespace-pre-wrap leading-4">
- {preview()}
- </pre>
- )}
- </Show>
</div>
)}
</Show>
diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx
index b2d9747c7..dea9c3d44 100644
--- a/packages/app/src/pages/session.tsx
+++ b/packages/app/src/pages/session.tsx
@@ -2074,13 +2074,14 @@ export default function Page() {
>
<button
type="button"
- class="flex items-center gap-2 px-2 py-1 rounded-md bg-surface-raised-stronger-non-alpha border border-border-base text-12-regular text-text-strong hover:bg-surface-raised-base-hover"
+ class="group relative flex items-center gap-2 h-6 px-2.5 rounded-md bg-surface-raised-stronger-non-alpha border border-border-weak-base text-12-medium text-text-strong shadow-xs-border whitespace-nowrap hover:bg-surface-raised-stronger-hover hover:border-border-hover focus:outline-none focus-visible:shadow-xs-border-focus"
onClick={() => {
const p = path()
if (!p) return
addSelectionToContext(p, sel())
}}
>
+ <span class="pointer-events-none absolute -left-1 top-1/2 size-2.5 -translate-y-1/2 rotate-45 bg-surface-raised-stronger-non-alpha border-l border-b border-border-weak-base group-hover:bg-surface-raised-stronger-hover group-hover:border-border-hover" />
<Icon name="plus-small" size="small" />
<span>
{language.t("session.context.addToContext", {
diff --git a/packages/ui/src/components/markdown.css b/packages/ui/src/components/markdown.css
index a30510a8d..ef4318733 100644
--- a/packages/ui/src/components/markdown.css
+++ b/packages/ui/src/components/markdown.css
@@ -60,8 +60,8 @@
ol {
margin-top: 0.5rem;
margin-bottom: 1rem;
- padding-left: 0;
- list-style-position: inside;
+ padding-left: 1.5rem;
+ list-style-position: outside;
}
ul {
@@ -76,6 +76,16 @@
margin-bottom: 0.5rem;
}
+ li > p:first-child {
+ display: inline;
+ margin: 0;
+ }
+
+ li > p + p {
+ display: block;
+ margin-top: 0.5rem;
+ }
+
li::marker {
color: var(--text-weak);
}
diff --git a/packages/ui/src/components/popover.tsx b/packages/ui/src/components/popover.tsx
index 6db44f5f9..8552f601a 100644
--- a/packages/ui/src/components/popover.tsx
+++ b/packages/ui/src/components/popover.tsx
@@ -13,6 +13,7 @@ export interface PopoverProps<T extends ValidComponent = "div">
description?: JSXElement
class?: ComponentProps<"div">["class"]
classList?: ComponentProps<"div">["classList"]
+ portal?: boolean
}
export function Popover<T extends ValidComponent = "div">(props: PopoverProps<T>) {
@@ -26,40 +27,45 @@ export function Popover<T extends ValidComponent = "div">(props: PopoverProps<T>
"class",
"classList",
"children",
+ "portal",
])
+ const content = () => (
+ <Kobalte.Content
+ data-component="popover-content"
+ classList={{
+ ...(local.classList ?? {}),
+ [local.class ?? ""]: !!local.class,
+ }}
+ >
+ {/* <Kobalte.Arrow data-slot="popover-arrow" /> */}
+ <Show when={local.title}>
+ <div data-slot="popover-header">
+ <Kobalte.Title data-slot="popover-title">{local.title}</Kobalte.Title>
+ <Kobalte.CloseButton
+ data-slot="popover-close-button"
+ as={IconButton}
+ icon="close"
+ variant="ghost"
+ aria-label={i18n.t("ui.common.close")}
+ />
+ </div>
+ </Show>
+ <Show when={local.description}>
+ <Kobalte.Description data-slot="popover-description">{local.description}</Kobalte.Description>
+ </Show>
+ <div data-slot="popover-body">{local.children}</div>
+ </Kobalte.Content>
+ )
+
return (
<Kobalte gutter={4} {...rest}>
<Kobalte.Trigger as={local.triggerAs ?? "div"} data-slot="popover-trigger" {...(local.triggerProps as any)}>
{local.trigger}
</Kobalte.Trigger>
- <Kobalte.Portal>
- <Kobalte.Content
- data-component="popover-content"
- classList={{
- ...(local.classList ?? {}),
- [local.class ?? ""]: !!local.class,
- }}
- >
- {/* <Kobalte.Arrow data-slot="popover-arrow" /> */}
- <Show when={local.title}>
- <div data-slot="popover-header">
- <Kobalte.Title data-slot="popover-title">{local.title}</Kobalte.Title>
- <Kobalte.CloseButton
- data-slot="popover-close-button"
- as={IconButton}
- icon="close"
- variant="ghost"
- aria-label={i18n.t("ui.common.close")}
- />
- </div>
- </Show>
- <Show when={local.description}>
- <Kobalte.Description data-slot="popover-description">{local.description}</Kobalte.Description>
- </Show>
- <div data-slot="popover-body">{local.children}</div>
- </Kobalte.Content>
- </Kobalte.Portal>
+ <Show when={local.portal ?? true} fallback={content()}>
+ <Kobalte.Portal>{content()}</Kobalte.Portal>
+ </Show>
</Kobalte>
)
}
diff --git a/packages/ui/src/components/session-review.css b/packages/ui/src/components/session-review.css
index 775d3d444..d271da5f9 100644
--- a/packages/ui/src/components/session-review.css
+++ b/packages/ui/src/components/session-review.css
@@ -198,6 +198,7 @@
[data-slot="session-review-diff-wrapper"] {
position: relative;
+ overflow: hidden;
}
[data-slot="session-review-comment-anchor"] {
@@ -213,15 +214,15 @@
display: flex;
align-items: center;
justify-content: center;
- background: var(--surface-base);
- border: 1px solid color-mix(in oklch, var(--icon-info-active) 60%, transparent);
- color: var(--icon-info-active);
- box-shadow: var(--shadow-xs-border);
+ background: var(--surface-warning-base);
+ border: 1px solid var(--border-warning-base);
+ color: var(--icon-warning-active);
+ box-shadow: var(--shadow-xs);
cursor: pointer;
&:hover {
- background: var(--surface-raised-base-hover);
- border-color: var(--icon-info-active);
+ background: var(--surface-warning-weak);
+ border-color: var(--border-warning-hover);
}
&:focus {
@@ -240,6 +241,12 @@
max-width: 320px;
}
+ [data-slot="session-review-comment-popover"] {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ }
+
[data-slot="session-review-comment-hover-label"],
[data-slot="session-review-comment-popover-label"] {
font-family: var(--font-family-sans);
diff --git a/packages/ui/src/components/session-review.tsx b/packages/ui/src/components/session-review.tsx
index 7afebdced..f3e7736f8 100644
--- a/packages/ui/src/components/session-review.tsx
+++ b/packages/ui/src/components/session-review.tsx
@@ -581,6 +581,7 @@ export const SessionReview = (props: SessionReviewProps) => {
}}
>
<Popover
+ portal={false}
open={isCommentOpen(comment)}
onOpenChange={(open) => {
if (open) {
@@ -632,6 +633,7 @@ export const SessionReview = (props: SessionReviewProps) => {
<Show when={draftTop() !== undefined}>
<div data-slot="session-review-comment-anchor" style={{ top: `${draftTop() ?? 0}px` }}>
<Popover
+ portal={false}
open={true}
onOpenChange={(open) => {
if (open) return
diff --git a/packages/ui/src/pierre/index.ts b/packages/ui/src/pierre/index.ts
index 16bc08f86..cef515613 100644
--- a/packages/ui/src/pierre/index.ts
+++ b/packages/ui/src/pierre/index.ts
@@ -32,11 +32,12 @@ const unsafeCSS = `
--diffs-bg-addition-number: var(--diffs-bg-addition-number-override, light-dark( color-mix(in lab, var(--diffs-bg) 91%, var(--diffs-addition-base)), color-mix(in lab, var(--diffs-bg) 85%, var(--diffs-addition-base))));
--diffs-bg-addition-hover: var(--diffs-bg-addition-hover-override, light-dark( color-mix(in lab, var(--diffs-bg) 80%, var(--diffs-addition-base)), color-mix(in lab, var(--diffs-bg) 70%, var(--diffs-addition-base))));
--diffs-bg-addition-emphasis: var(--diffs-bg-addition-emphasis-override, light-dark(rgb(from var(--diffs-addition-base) r g b / 0.07), rgb(from var(--diffs-addition-base) r g b / 0.1)));
- --diffs-selection-base: var(--text-interactive-base);
- --diffs-selection-number-fg: light-dark( color-mix(in lab, var(--diffs-selection-base) 65%, var(--diffs-mixer)), color-mix(in lab, var(--diffs-selection-base) 75%, var(--diffs-mixer)));
- --diffs-bg-selection: var(--diffs-bg-selection-override, rgb(from var(--diffs-selection-base) r g b / 0.18));
- --diffs-bg-selection-number: var(--diffs-bg-selection-number-override, rgb(from var(--diffs-selection-base) r g b / 0.22));
- --diffs-bg-selection-text: rgb(from var(--diffs-selection-base) r g b / 0.12);
+ --diffs-selection-base: var(--surface-warning-strong);
+ --diffs-selection-border: var(--border-warning-base);
+ --diffs-selection-number-fg: var(--text-on-warning-strong);
+ --diffs-bg-selection: var(--diffs-bg-selection-override, color-mix(in oklch, var(--surface-warning-base) 65%, transparent));
+ --diffs-bg-selection-number: var(--diffs-bg-selection-number-override, color-mix(in oklch, var(--surface-warning-base) 85%, transparent));
+ --diffs-bg-selection-text: color-mix(in oklch, var(--surface-warning-strong) 20%, transparent);
}
[data-diffs] ::selection {
@@ -52,6 +53,16 @@ const unsafeCSS = `
color: var(--diffs-selection-number-fg);
}
+[data-diffs] [data-selected-line] {
+ background-color: var(--diffs-bg-selection);
+ box-shadow: inset 2px 0 0 var(--diffs-selection-border);
+}
+
+[data-diffs] [data-selected-line] [data-column-number] {
+ background-color: var(--diffs-bg-selection-number);
+ color: var(--diffs-selection-number-fg);
+}
+
[data-diffs-header],
[data-diffs] {
[data-separator-wrapper] {
diff --git a/packages/ui/src/styles/base.css b/packages/ui/src/styles/base.css
index 729ff045a..33a245705 100644
--- a/packages/ui/src/styles/base.css
+++ b/packages/ui/src/styles/base.css
@@ -79,6 +79,11 @@ a {
color: inherit;
-webkit-text-decoration: inherit;
text-decoration: inherit;
+ cursor: default;
+}
+
+*[data-tauri-drag-region] {
+ app-region: drag;
}
/*