diff --git a/packages/app/src/components/session/session-sortable-tab.tsx b/packages/app/src/components/session/session-sortable-tab.tsx index 516f3c8edeb1..5e2edcfcd83c 100644 --- a/packages/app/src/components/session/session-sortable-tab.tsx +++ b/packages/app/src/components/session/session-sortable-tab.tsx @@ -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 (
@@ -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() @@ -35,28 +44,76 @@ export function SortableTab(props: { tab: string; onTabClose: (tab: string) => v // @ts-ignore
- + props.onTabClose(props.tab)} + aria-label={language.t("common.closeTab")} + /> + + } + hideCloseButton + onMiddleClick={() => props.onTabClose(props.tab)} > - props.onTabClose(props.tab)} - aria-label={language.t("common.closeTab")} - /> - + {(p) => } + } - hideCloseButton - onMiddleClick={() => props.onTabClose(props.tab)} > - {(p) => } - + + + props.onTabClose(props.tab)} + aria-label={language.t("common.closeTab")} + /> + + } + hideCloseButton + onMiddleClick={() => props.onTabClose(props.tab)} + onClick={props.onClick} + > + {(p) => } + + + + props.onTabClose(props.tab)}> + {language.t("common.close")} + + + props.onCloseOthers?.(props.tab)}> + {language.t("tab.context.closeOthers")} + + + + props.onMention?.(props.tab)}> + {language.t("session.files.mention")} + + + + +
) diff --git a/packages/app/src/context/layout.tsx b/packages/app/src/context/layout.tsx index 8d9c865f8495..41b06dcf9446 100644 --- a/packages/app/src/context/layout.tsx +++ b/packages/app/src/context/layout.tsx @@ -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] diff --git a/packages/app/src/i18n/ar.ts b/packages/app/src/i18n/ar.ts index 77a3edb062a2..cc3385bc1df9 100644 --- a/packages/app/src/i18n/ar.ts +++ b/packages/app/src/i18n/ar.ts @@ -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": "تغيير", diff --git a/packages/app/src/i18n/br.ts b/packages/app/src/i18n/br.ts index a743a3d89691..345291172b7e 100644 --- a/packages/app/src/i18n/br.ts +++ b/packages/app/src/i18n/br.ts @@ -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", diff --git a/packages/app/src/i18n/bs.ts b/packages/app/src/i18n/bs.ts index ce37989c259b..fa5e9771a745 100644 --- a/packages/app/src/i18n/bs.ts +++ b/packages/app/src/i18n/bs.ts @@ -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", diff --git a/packages/app/src/i18n/da.ts b/packages/app/src/i18n/da.ts index 88704607b339..b32c336610bc 100644 --- a/packages/app/src/i18n/da.ts +++ b/packages/app/src/i18n/da.ts @@ -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", diff --git a/packages/app/src/i18n/de.ts b/packages/app/src/i18n/de.ts index a4d12d445432..444434f4e5ff 100644 --- a/packages/app/src/i18n/de.ts +++ b/packages/app/src/i18n/de.ts @@ -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", diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index 4d7d571afb7e..1c30816822c0 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -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", diff --git a/packages/app/src/i18n/es.ts b/packages/app/src/i18n/es.ts index 5d48ba494935..1c1530b4e0ef 100644 --- a/packages/app/src/i18n/es.ts +++ b/packages/app/src/i18n/es.ts @@ -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", diff --git a/packages/app/src/i18n/fr.ts b/packages/app/src/i18n/fr.ts index a76e57ff1534..3060d4e121cd 100644 --- a/packages/app/src/i18n/fr.ts +++ b/packages/app/src/i18n/fr.ts @@ -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", diff --git a/packages/app/src/i18n/ja.ts b/packages/app/src/i18n/ja.ts index e41dea9dc735..21322f6e6a42 100644 --- a/packages/app/src/i18n/ja.ts +++ b/packages/app/src/i18n/ja.ts @@ -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": "変更", diff --git a/packages/app/src/i18n/ko.ts b/packages/app/src/i18n/ko.ts index a4f42a583e02..17e57b1177d7 100644 --- a/packages/app/src/i18n/ko.ts +++ b/packages/app/src/i18n/ko.ts @@ -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": "변경", diff --git a/packages/app/src/i18n/no.ts b/packages/app/src/i18n/no.ts index 3de7837f8003..f82c34cbf174 100644 --- a/packages/app/src/i18n/no.ts +++ b/packages/app/src/i18n/no.ts @@ -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", diff --git a/packages/app/src/i18n/pl.ts b/packages/app/src/i18n/pl.ts index 44bc4677be95..b8d4c7e73a04 100644 --- a/packages/app/src/i18n/pl.ts +++ b/packages/app/src/i18n/pl.ts @@ -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", diff --git a/packages/app/src/i18n/ru.ts b/packages/app/src/i18n/ru.ts index 28785c0e9fbc..ff38783b22c2 100644 --- a/packages/app/src/i18n/ru.ts +++ b/packages/app/src/i18n/ru.ts @@ -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": "Изменение", diff --git a/packages/app/src/i18n/th.ts b/packages/app/src/i18n/th.ts index 9858f39d772b..9d898f6adb61 100644 --- a/packages/app/src/i18n/th.ts +++ b/packages/app/src/i18n/th.ts @@ -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": "การเปลี่ยนแปลง", diff --git a/packages/app/src/i18n/zh.ts b/packages/app/src/i18n/zh.ts index a8fda6f3a601..38d9e3fb6ca3 100644 --- a/packages/app/src/i18n/zh.ts +++ b/packages/app/src/i18n/zh.ts @@ -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": "更改", diff --git a/packages/app/src/i18n/zht.ts b/packages/app/src/i18n/zht.ts index 319f5c51d15c..a7803b4881cc 100644 --- a/packages/app/src/i18n/zht.ts +++ b/packages/app/src/i18n/zht.ts @@ -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": "變更", diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 31f9e3fb7c3e..f9786242af26 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -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 @@ -1727,6 +1784,8 @@ export default function Page() { kinds={kinds()} activeDiff={tree.activeDiff} focusReviewDiff={focusReviewDiff} + onMention={mentionTabInPrompt} + onCloseOthers={closeOtherTabs} />
diff --git a/packages/app/src/pages/session/session-side-panel.tsx b/packages/app/src/pages/session/session-side-panel.tsx index d9460cc1a765..7b155a75d936 100644 --- a/packages/app/src/pages/session/session-side-panel.tsx +++ b/packages/app/src/pages/session/session-side-panel.tsx @@ -67,6 +67,8 @@ export function SessionSidePanel(props: { kinds: Map activeDiff?: string focusReviewDiff: (path: string) => void + onMention?: (tab: string) => void + onCloseOthers?: (tab: string) => void }) { return ( @@ -74,11 +76,6 @@ export function SessionSidePanel(props: { 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` }} >
@@ -138,7 +135,15 @@ export function SessionSidePanel(props: { - {(tab) => } + {(tab) => ( + props.openTab(tab)} + /> + )}