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