Quellcode durchsuchen

feat(plugin): add ui.tabs API for session tab control (#39591)

Kit Langton vor 2 Wochen
Ursprung
Commit
78c139b8b5
2 geänderte Dateien mit 48 neuen und 0 gelöschten Zeilen
  1. 19 0
      packages/plugin/src/tui/context.ts
  2. 29 0
      packages/tui/src/plugin/context.tsx

+ 19 - 0
packages/plugin/src/tui/context.ts

@@ -351,6 +351,25 @@ export interface UI {
     navigate(destination: Destination): void
     current(): Route
   }
+  readonly tabs: {
+    /** Returns whether session tabs are enabled for this TUI. */
+    enabled(): boolean
+    /** Returns the currently open root-session tabs. Reactive when read in a Solid computation. */
+    list(): readonly {
+      readonly sessionID: string
+      readonly title?: string
+      readonly active: boolean
+      readonly busy: boolean
+      readonly attention: boolean
+      readonly unread?: "activity" | "error"
+    }[]
+    /** Opens (or focuses) a tab for a session, adding it when not already open. Returns false when tabs are disabled. */
+    open(sessionID: string): boolean
+    /** Focuses an already-open tab and returns false when it is not open. */
+    focus(sessionID: string): boolean
+    /** Closes an open tab, or the active tab when omitted, and returns false when no tab matched. */
+    close(sessionID?: string): boolean
+  }
   readonly slot: <Name extends SlotName>(name: Name, render: Slot<Name>) => () => void
 }
 

+ 29 - 0
packages/tui/src/plugin/context.tsx

@@ -33,6 +33,7 @@ import { useDialog } from "../ui/dialog"
 import { useToast } from "../ui/toast"
 import { useAttention } from "../context/attention"
 import { useStorage } from "../context/storage"
+import { useSessionTabs } from "../context/session-tabs"
 import { abbreviateHome } from "../util/path-format"
 import { builtins } from "./builtins"
 import { discoverTuiPlugins } from "./discovery"
@@ -94,6 +95,7 @@ export function PluginProvider(props: ParentProps<{ packages: PackageResolver }>
   const toast = useToast()
   const attention = useAttention()
   const storage = useStorage()
+  const sessionTabs = useSessionTabs()
   const directory = config.path ? path.dirname(config.path) : process.cwd()
   const [store, setStore] = createStore({
     ready: false,
@@ -278,6 +280,33 @@ export function PluginProvider(props: ParentProps<{ packages: PackageResolver }>
             return route.data
           },
         },
+        tabs: {
+          enabled: sessionTabs.enabled,
+          list: () =>
+            sessionTabs.tabs().map((tab) => ({
+              ...tab,
+              active: sessionTabs.current() === tab.sessionID,
+              ...sessionTabs.status(tab.sessionID),
+            })),
+          open(sessionID) {
+            if (!sessionTabs.enabled()) return false
+            sessionTabs.select(sessionID)
+            return true
+          },
+          focus(sessionID) {
+            if (!sessionTabs.enabled()) return false
+            if (!sessionTabs.tabs().some((tab) => tab.sessionID === sessionID)) return false
+            sessionTabs.select(sessionID)
+            return true
+          },
+          close(sessionID) {
+            if (!sessionTabs.enabled()) return false
+            const target = sessionID ?? sessionTabs.current()
+            if (!target || !sessionTabs.tabs().some((tab) => tab.sessionID === target)) return false
+            sessionTabs.close(target)
+            return true
+          },
+        },
         slot(name, render) {
           if (store.registrations[item.plugin.id]?.slots[name]) throw new Error(`Slot already registered: ${name}`)
           setStore("registrations", item.plugin.id, "slots", name, () => (input: SlotMap[typeof name]) => (