Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 77 additions & 20 deletions packages/app/src/components/session/session-sortable-tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,20 @@ import { FileIcon } from "@opencode-ai/ui/file-icon"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { TooltipKeybind } from "@opencode-ai/ui/tooltip"
import { Tabs } from "@opencode-ai/ui/tabs"
import { ContextMenu } from "@opencode-ai/ui/context-menu"
import { getFilename } from "@opencode-ai/util/path"
import { useFile } from "@/context/file"
import { useLanguage } from "@/context/language"
import { useCommand } from "@/context/command"

interface SortableTabProps {
readonly tab: string
readonly onTabClose: (tab: string) => void
readonly onMention?: (tab: string) => void
readonly onCloseOthers?: (currentTab: string) => void
readonly onClick?: () => void
}

export function FileVisual(props: { path: string; active?: boolean }): JSX.Element {
return (
<div class="flex items-center gap-x-1.5 min-w-0">
Expand All @@ -25,7 +34,7 @@ export function FileVisual(props: { path: string; active?: boolean }): JSX.Eleme
)
}

export function SortableTab(props: { tab: string; onTabClose: (tab: string) => void }): JSX.Element {
export function SortableTab(props: SortableTabProps): JSX.Element {
const file = useFile()
const language = useLanguage()
const command = useCommand()
Expand All @@ -35,28 +44,76 @@ export function SortableTab(props: { tab: string; onTabClose: (tab: string) => v
// @ts-ignore
<div use:sortable classList={{ "h-full": true, "opacity-0": sortable.isActiveDraggable }}>
<div class="relative h-full">
<Tabs.Trigger
value={props.tab}
closeButton={
<TooltipKeybind
title={language.t("common.closeTab")}
keybind={command.keybind("tab.close")}
placement="bottom"
<Show
when={props.onMention}
fallback={
<Tabs.Trigger
value={props.tab}
closeButton={
<TooltipKeybind
title={language.t("common.closeTab")}
keybind={command.keybind("tab.close")}
placement="bottom"
>
<IconButton
icon="close-small"
variant="ghost"
class="h-5 w-5"
onClick={() => props.onTabClose(props.tab)}
aria-label={language.t("common.closeTab")}
/>
</TooltipKeybind>
}
hideCloseButton
onMiddleClick={() => props.onTabClose(props.tab)}
>
<IconButton
icon="close-small"
variant="ghost"
class="h-5 w-5"
onClick={() => props.onTabClose(props.tab)}
aria-label={language.t("common.closeTab")}
/>
</TooltipKeybind>
<Show when={path()}>{(p) => <FileVisual path={p()} />}</Show>
</Tabs.Trigger>
}
hideCloseButton
onMiddleClick={() => props.onTabClose(props.tab)}
>
<Show when={path()}>{(p) => <FileVisual path={p()} />}</Show>
</Tabs.Trigger>
<ContextMenu>
<ContextMenu.Trigger
as={Tabs.Trigger}
value={props.tab}
closeButton={
<TooltipKeybind
title={language.t("common.closeTab")}
keybind={command.keybind("tab.close")}
placement="bottom"
>
<IconButton
icon="close-small"
variant="ghost"
class="h-5 w-5"
onClick={() => props.onTabClose(props.tab)}
aria-label={language.t("common.closeTab")}
/>
</TooltipKeybind>
}
hideCloseButton
onMiddleClick={() => props.onTabClose(props.tab)}
onClick={props.onClick}
>
<Show when={path()}>{(p) => <FileVisual path={p()} />}</Show>
</ContextMenu.Trigger>
<ContextMenu.Portal>
<ContextMenu.Content>
<ContextMenu.Item onSelect={() => props.onTabClose(props.tab)}>
<ContextMenu.ItemLabel>{language.t("common.close")}</ContextMenu.ItemLabel>
</ContextMenu.Item>
<Show when={props.onCloseOthers}>
<ContextMenu.Item onSelect={() => props.onCloseOthers?.(props.tab)}>
<ContextMenu.ItemLabel>{language.t("tab.context.closeOthers")}</ContextMenu.ItemLabel>
</ContextMenu.Item>
</Show>
<ContextMenu.Separator />
<ContextMenu.Item onSelect={() => props.onMention?.(props.tab)}>
<ContextMenu.ItemLabel>{language.t("session.files.mention")}</ContextMenu.ItemLabel>
</ContextMenu.Item>
</ContextMenu.Content>
</ContextMenu.Portal>
</ContextMenu>
</Show>
</div>
</div>
)
Expand Down
11 changes: 11 additions & 0 deletions packages/app/src/context/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,17 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
setStore("sessionTabs", session, "active", next)
})
},
closeOthers(tab: string) {
const session = key()
const current = store.sessionTabs[session]
if (!current) return
const all = [tab, "context", "review"].filter((x) => current.all.includes(x))
const active = current.active === tab || all.includes(current.active!) ? current.active! : tab
batch(() => {
setStore("sessionTabs", session, "all", all)
setStore("sessionTabs", session, "active", active)
})
},
move(tab: string, to: number) {
const session = key()
const current = store.sessionTabs[session]
Expand Down
2 changes: 2 additions & 0 deletions packages/app/src/i18n/ar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,8 @@ export const dict = {
"session.tab.session": "جلسة",
"session.tab.review": "مراجعة",
"session.tab.context": "سياق",
"tab.context.closeOthers": "إغلاق البقية",
"session.files.mention": "إشارة",
"session.panel.reviewAndFiles": "المراجعة والملفات",
"session.review.filesChanged": "تم تغيير {{count}} ملفات",
"session.review.change.one": "تغيير",
Expand Down
2 changes: 2 additions & 0 deletions packages/app/src/i18n/br.ts
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,8 @@ export const dict = {
"session.tab.session": "Sessão",
"session.tab.review": "Revisão",
"session.tab.context": "Contexto",
"tab.context.closeOthers": "Fechar outras",
"session.files.mention": "Mencionar",
"session.panel.reviewAndFiles": "Revisão e arquivos",
"session.review.filesChanged": "{{count}} Arquivos Alterados",
"session.review.change.one": "Alteração",
Expand Down
2 changes: 2 additions & 0 deletions packages/app/src/i18n/bs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,8 @@ export const dict = {
"session.tab.session": "Sesija",
"session.tab.review": "Pregled",
"session.tab.context": "Kontekst",
"tab.context.closeOthers": "Zatvori ostale",
"session.files.mention": "Spomeni",
"session.panel.reviewAndFiles": "Pregled i datoteke",
"session.review.filesChanged": "Izmijenjeno {{count}} datoteka",
"session.review.change.one": "Izmjena",
Expand Down
2 changes: 2 additions & 0 deletions packages/app/src/i18n/da.ts
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,8 @@ export const dict = {
"session.tab.session": "Session",
"session.tab.review": "Gennemgang",
"session.tab.context": "Kontekst",
"tab.context.closeOthers": "Luk andre",
"session.files.mention": "Nævn",
"session.panel.reviewAndFiles": "Gennemgang og filer",
"session.review.filesChanged": "{{count}} Filer ændret",
"session.review.change.one": "Ændring",
Expand Down
2 changes: 2 additions & 0 deletions packages/app/src/i18n/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,8 @@ export const dict = {
"session.tab.session": "Sitzung",
"session.tab.review": "Überprüfung",
"session.tab.context": "Kontext",
"tab.context.closeOthers": "Andere schließen",
"session.files.mention": "Erwähnen",
"session.panel.reviewAndFiles": "Überprüfung und Dateien",
"session.review.filesChanged": "{{count}} Dateien geändert",
"session.review.change.one": "Änderung",
Expand Down
2 changes: 2 additions & 0 deletions packages/app/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,8 @@ export const dict = {
"session.tab.session": "Session",
"session.tab.review": "Review",
"session.tab.context": "Context",
"tab.context.closeOthers": "Close others",
"session.files.mention": "Mention",
"session.panel.reviewAndFiles": "Review and files",
"session.review.filesChanged": "{{count}} Files Changed",
"session.review.change.one": "Change",
Expand Down
2 changes: 2 additions & 0 deletions packages/app/src/i18n/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,8 @@ export const dict = {
"session.tab.session": "Sesión",
"session.tab.review": "Revisión",
"session.tab.context": "Contexto",
"tab.context.closeOthers": "Cerrar otras",
"session.files.mention": "Mencionar",
"session.panel.reviewAndFiles": "Revisión y archivos",
"session.review.filesChanged": "{{count}} Archivos Cambiados",
"session.review.change.one": "Cambio",
Expand Down
2 changes: 2 additions & 0 deletions packages/app/src/i18n/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,8 @@ export const dict = {
"session.tab.session": "Session",
"session.tab.review": "Revue",
"session.tab.context": "Contexte",
"tab.context.closeOthers": "Fermer les autres",
"session.files.mention": "Mentionner",
"session.panel.reviewAndFiles": "Revue et fichiers",
"session.review.filesChanged": "{{count}} fichiers modifiés",
"session.review.change.one": "Modification",
Expand Down
2 changes: 2 additions & 0 deletions packages/app/src/i18n/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,8 @@ export const dict = {
"session.tab.session": "セッション",
"session.tab.review": "レビュー",
"session.tab.context": "コンテキスト",
"tab.context.closeOthers": "他のタブを閉じる",
"session.files.mention": "メンション",
"session.panel.reviewAndFiles": "レビューとファイル",
"session.review.filesChanged": "{{count}} ファイル変更",
"session.review.change.one": "変更",
Expand Down
2 changes: 2 additions & 0 deletions packages/app/src/i18n/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,8 @@ export const dict = {
"session.tab.session": "세션",
"session.tab.review": "검토",
"session.tab.context": "컨텍스트",
"tab.context.closeOthers": "다른 탭 닫기",
"session.files.mention": "멘션",
"session.panel.reviewAndFiles": "검토 및 파일",
"session.review.filesChanged": "{{count}}개 파일 변경됨",
"session.review.change.one": "변경",
Expand Down
2 changes: 2 additions & 0 deletions packages/app/src/i18n/no.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,8 @@ export const dict = {
"session.tab.session": "Sesjon",
"session.tab.review": "Gjennomgang",
"session.tab.context": "Kontekst",
"tab.context.closeOthers": "Lukk andre",
"session.files.mention": "Nevn",
"session.panel.reviewAndFiles": "Gjennomgang og filer",
"session.review.filesChanged": "{{count}} filer endret",
"session.review.change.one": "Endring",
Expand Down
2 changes: 2 additions & 0 deletions packages/app/src/i18n/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,8 @@ export const dict = {
"session.tab.session": "Sesja",
"session.tab.review": "Przegląd",
"session.tab.context": "Kontekst",
"tab.context.closeOthers": "Zamknij pozostałe",
"session.files.mention": "Wspomnij",
"session.panel.reviewAndFiles": "Przegląd i pliki",
"session.review.filesChanged": "Zmieniono {{count}} plików",
"session.review.change.one": "Zmiana",
Expand Down
2 changes: 2 additions & 0 deletions packages/app/src/i18n/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,8 @@ export const dict = {
"session.tab.session": "Сессия",
"session.tab.review": "Обзор",
"session.tab.context": "Контекст",
"tab.context.closeOthers": "Закрыть остальные",
"session.files.mention": "Упомянуть",
"session.panel.reviewAndFiles": "Обзор и файлы",
"session.review.filesChanged": "{{count}} файлов изменено",
"session.review.change.one": "Изменение",
Expand Down
2 changes: 2 additions & 0 deletions packages/app/src/i18n/th.ts
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,8 @@ export const dict = {
"session.tab.session": "เซสชัน",
"session.tab.review": "ตรวจสอบ",
"session.tab.context": "บริบท",
"tab.context.closeOthers": "ปิดแท็บอื่นๆ",
"session.files.mention": "กล่าวถึง",
"session.panel.reviewAndFiles": "ตรวจสอบและไฟล์",
"session.review.filesChanged": "{{count}} ไฟล์ที่เปลี่ยนแปลง",
"session.review.change.one": "การเปลี่ยนแปลง",
Expand Down
2 changes: 2 additions & 0 deletions packages/app/src/i18n/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,8 @@ export const dict = {
"session.tab.session": "会话",
"session.tab.review": "审查",
"session.tab.context": "上下文",
"tab.context.closeOthers": "关闭其他",
"session.files.mention": "提及",
"session.panel.reviewAndFiles": "审查和文件",
"session.review.filesChanged": "{{count}} 个文件变更",
"session.review.change.one": "更改",
Expand Down
2 changes: 2 additions & 0 deletions packages/app/src/i18n/zht.ts
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,8 @@ export const dict = {
"session.tab.session": "工作階段",
"session.tab.review": "審查",
"session.tab.context": "上下文",
"tab.context.closeOthers": "關閉其他",
"session.files.mention": "提及",
"session.panel.reviewAndFiles": "審查與檔案",
"session.review.filesChanged": "{{count}} 個檔案變更",
"session.review.change.one": "變更",
Expand Down
59 changes: 59 additions & 0 deletions packages/app/src/pages/session.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,63 @@ export default function Page() {
prompt.context.add({ type: "file", path, selection, preview })
}

const mentionTabInPrompt = (tab: string) => {
const path = file.pathFromTab(tab)
if (!path) return
const content = "@" + path
const current = prompt.current()
const textLength = current.reduce((len, part) => len + ("content" in part ? part.content.length : 0), 0)

const needsSpace =
textLength > 0 &&
!current.some((part) => {
if (part.type !== "text") return false
return part.content.endsWith(" ") || part.content.endsWith("\n")
})

const parts = [...current]
let position = textLength

if (needsSpace) {
const lastTextIndex = parts.findLastIndex((p) => p.type === "text")
if (lastTextIndex >= 0) {
const lastText = parts[lastTextIndex]
if (lastText.type === "text") {
parts[lastTextIndex] = {
...lastText,
content: lastText.content + " ",
end: lastText.end + 1,
}
position += 1
}
} else {
parts.push({ type: "text", content: " ", start: position, end: position + 1 })
position += 1
}
}

parts.push({
type: "file",
path,
content,
start: position,
end: position + content.length,
})
position += content.length

parts.push({ type: "text", content: " ", start: position, end: position + 1 })
position += 1

prompt.set(parts, position)
}

const closeOtherTabs = (currentTab: string) => {
const others = openedTabs().filter((tab) => tab !== currentTab)
for (const tab of others) {
tabs().close(tab)
}
}

const addCommentToContext = (input: {
file: string
selection: SelectedLineRange
Expand Down Expand Up @@ -1727,6 +1784,8 @@ export default function Page() {
kinds={kinds()}
activeDiff={tree.activeDiff}
focusReviewDiff={focusReviewDiff}
onMention={mentionTabInPrompt}
onCloseOthers={closeOtherTabs}
/>
</div>

Expand Down
17 changes: 11 additions & 6 deletions packages/app/src/pages/session/session-side-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,18 +67,15 @@ export function SessionSidePanel(props: {
kinds: Map<string, "add" | "del" | "mix">
activeDiff?: string
focusReviewDiff: (path: string) => void
onMention?: (tab: string) => void
onCloseOthers?: (tab: string) => void
}) {
return (
<Show when={props.open}>
<aside
id="review-panel"
aria-label={props.language.t("session.panel.reviewAndFiles")}
class="relative min-w-0 h-full border-l border-border-weak-base flex"
classList={{
"flex-1": props.reviewOpen,
"shrink-0": !props.reviewOpen,
}}
style={{ width: props.reviewOpen ? undefined : `${props.layout.fileTree.width()}px` }}
>
<Show when={props.reviewOpen}>
<div class="flex-1 min-w-0 h-full">
Expand Down Expand Up @@ -138,7 +135,15 @@ export function SessionSidePanel(props: {
</Show>
<SortableProvider ids={props.openedTabs()}>
<For each={props.openedTabs()}>
{(tab) => <SortableTab tab={tab} onTabClose={props.tabs().close} />}
{(tab) => (
<SortableTab
tab={tab}
onTabClose={props.tabs().close}
onMention={props.onMention}
onCloseOthers={props.onCloseOthers}
onClick={() => props.openTab(tab)}
/>
)}
</For>
</SortableProvider>
<StickyAddButton>
Expand Down
Loading