Procházet zdrojové kódy

refactor(tui): remove redundant code (#40081)

Kit Langton před 2 týdny
rodič
revize
5d729521d3

+ 0 - 2
packages/tui/src/component/dialog-status.tsx

@@ -4,8 +4,6 @@ import { useDialog } from "../ui/dialog"
 import { useData } from "../context/data"
 import { For, Match, Switch, Show, createMemo } from "solid-js"
 
-export type DialogStatusProps = {}
-
 export function DialogStatus() {
   const data = useData()
   const theme = useTheme("elevated")

+ 9 - 11
packages/tui/src/component/prompt/autocomplete.tsx

@@ -23,6 +23,7 @@ import { displayCharAt, mentionTriggerIndex } from "../../prompt/display"
 import type { FileSystemEntry } from "@opencode-ai/client"
 import { stringWidth } from "../../util/string-width"
 import { parseFileLineRange, stripFileLineRange } from "../../prompt/parse"
+import { moveSelection, revealSelectionOffset } from "../../ui/select-controller"
 
 export type AutocompleteRef = {
   onInput: (value: string) => void
@@ -501,22 +502,19 @@ export function Autocomplete(props: {
   function move(direction: -1 | 1) {
     if (!store.visible) return
     if (!options().length) return
-    let next = store.selected + direction
-    if (next < 0) next = options().length - 1
-    if (next >= options().length) next = 0
-    moveTo(next)
+    moveTo(moveSelection(store.selected, { count: options().length, delta: direction, policy: "wrap" }))
   }
 
   function moveTo(next: number) {
     setStore("selected", next)
     if (!scroll) return
-    const viewportHeight = Math.min(height(), options().length)
-    const scrollBottom = scroll.scrollTop + viewportHeight
-    if (next < scroll.scrollTop) {
-      scroll.scrollBy(next - scroll.scrollTop)
-    } else if (next + 1 > scrollBottom) {
-      scroll.scrollBy(next + 1 - scrollBottom)
-    }
+    const offset = revealSelectionOffset(scroll.scrollTop, {
+      count: options().length,
+      limit: Math.min(height(), options().length),
+      selected: next,
+    })
+    if (offset === scroll.scrollTop) return
+    scroll.scrollBy(offset - scroll.scrollTop)
   }
 
   function select() {

+ 0 - 3
packages/tui/src/component/prompt/index.tsx

@@ -8,7 +8,6 @@ import {
   type KeyEvent,
 } from "@opentui/core"
 import { createEffect, createMemo, onMount, createSignal, onCleanup, on, Show, Switch, Match } from "solid-js"
-import { registerOpencodeSpinner } from "../register-spinner"
 import path from "path"
 import { fileURLToPath } from "url"
 import { useLocal } from "../../context/local"
@@ -55,8 +54,6 @@ import { Keymap, type KeymapCommand } from "../../context/keymap"
 import { abbreviateHome } from "../../runtime"
 import { PluginSlot } from "../../plugin/render"
 
-registerOpencodeSpinner()
-
 export type PromptProps = {
   sessionID?: string
   visible?: boolean

+ 4 - 8
packages/tui/src/component/session-tabs.tsx

@@ -65,6 +65,8 @@ function VerticalSessionTabs(props: { controller?: SessionTabsController; animat
   const accent = () => theme.hue.accent[hueStep()]
   const activeNumber = () => theme.hue.interactive[hueStep()]
   const idleNumber = () => tint(theme.text.subdued, theme.background.default, 0.35)
+  const separatorUpperPulseColor = createMemo(() => tint(theme.background.default, theme.text.default, 0.04))
+  const separatorLowerPulseColor = createMemo(() => tint(theme.background.default, theme.text.default, 0.05))
   const [hovered, setHovered] = createSignal<string>()
   const [dragging, setDragging] = createSignal<string>()
   const [preview, setPreview] = createSignal<{ sessionID: string; index: number }>()
@@ -199,12 +201,6 @@ function VerticalSessionTabs(props: { controller?: SessionTabsController; animat
               }
               const separatorUpperColor = createMemo(() => tint(theme.background.default, previousGlowHue(), 0.1))
               const separatorLowerColor = createMemo(() => tint(theme.background.default, glowHue(), 0.12))
-              const separatorUpperPulseColor = createMemo(() =>
-                tint(theme.background.default, theme.text.default, 0.04),
-              )
-              const separatorLowerPulseColor = createMemo(() =>
-                tint(theme.background.default, theme.text.default, 0.05),
-              )
               const titleColor = (index: number) => {
                 const color = glows()
                   ? glowTextColor(foreground(), glowColor(), 1 + numberWidth() + index, width())
@@ -297,7 +293,7 @@ function VerticalSessionTabs(props: { controller?: SessionTabsController; animat
                   <box height={1} width="100%" flexDirection="row" position="relative">
                     <TabPulse
                       enabled={animations()}
-                      active={status().busy && !status().attention}
+                      active={runs()}
                       promptPulse={status().promptPulse}
                       complete={complete() && !status().attention}
                       glow={glows()}
@@ -350,7 +346,7 @@ function VerticalSessionTabs(props: { controller?: SessionTabsController; animat
                   <box height={1} width="100%" position="relative" flexDirection="row">
                     <TabPulse
                       enabled={animations()}
-                      active={status().busy && !status().attention}
+                      active={runs()}
                       promptPulse={status().promptPulse}
                       complete={complete() && !status().attention}
                       glow={glows()}

+ 0 - 2
packages/tui/src/context/route.tsx

@@ -67,8 +67,6 @@ function initialRoute(value: unknown): Route | undefined {
   }
 }
 
-export type RouteContext = ReturnType<typeof useRoute>
-
 export function useRouteData<T extends Route["type"]>(type: T) {
   const route = useRoute()
   return route.data as Extract<Route, { type: typeof type }>

+ 9 - 13
packages/tui/src/feature-plugins/prompt/footer.tsx

@@ -10,24 +10,18 @@ const money = new Intl.NumberFormat("en-US", {
 
 export function PromptFooter(props: { context: Plugin.Context; sessionID?: string; mode: "normal" | "shell" }) {
   const dimensions = useTerminalDimensions()
-  const activeSubagents = createMemo(() => {
+  const subagents = createMemo(() => {
     if (!props.sessionID) return 0
-    return props.context.data.session
+    const count = props.context.data.session
       .family(props.sessionID)
       .filter((id) => id !== props.sessionID && props.context.data.session.status(id) === "running").length
+    return count ? `${count} subagent${count === 1 ? "" : "s"}` : undefined
   })
-  const runningShells = createMemo(() => {
+  const shells = createMemo(() => {
     if (!props.sessionID) return 0
-    return props.context.data.shell
+    const count = props.context.data.shell
       .list(props.context.location)
       .filter((shell) => shell.metadata.sessionID === props.sessionID).length
-  })
-  const subagents = createMemo(() => {
-    const count = activeSubagents()
-    return count ? `${count} subagent${count === 1 ? "" : "s"}` : undefined
-  })
-  const shells = createMemo(() => {
-    const count = runningShells()
     return count ? `${count} shell${count === 1 ? "" : "s"}` : undefined
   })
   const status = createMemo(() => {
@@ -40,8 +34,10 @@ export function PromptFooter(props: { context: Plugin.Context; sessionID?: strin
       session.revert?.messageID,
     )
     const cost = props.context.data.session.cost(props.sessionID)
-    return [usage ? formatContextUsage(usage.tokens, usage.percent) : undefined, cost > 0 ? money.format(cost) : undefined]
-      .filter((item): item is string => Boolean(item))
+    return [
+      usage ? formatContextUsage(usage.tokens, usage.percent) : undefined,
+      cost > 0 ? money.format(cost) : undefined,
+    ].filter((item): item is string => Boolean(item))
   })
   const live = createMemo(() => Boolean(subagents() || shells()))
   const shortcut = (id: string) => props.context.keymap.shortcuts(id)[0]

+ 1 - 4
packages/tui/src/mini/runtime.queue.ts

@@ -90,10 +90,7 @@ export async function runPromptQueue(input: QueueInput): Promise<void> {
     draining = (async () => {
       try {
         while (!state.closed && state.queue.length > 0) {
-          const prompt = state.queue.shift()
-          if (!prompt) {
-            continue
-          }
+          const prompt = state.queue.shift()!
 
           if (prompt.mode !== "shell" && isNewCommand(prompt.text)) {
             if (!input.onNewSession) {

+ 8 - 6
packages/tui/src/routes/session/index.tsx

@@ -200,19 +200,21 @@ export function Session() {
   const diffWrapMode = createMemo(() => config.diffs?.wrap ?? "word")
   const groupExploration = createMemo(() => config.session?.grouping !== "none")
 
-  const tabRailWidth = createMemo(() =>
-    config.tabs?.enabled && config.tabs.vertical && sessionTabsFitVertically(dimensions().width)
-      ? SESSION_SIDEBAR_WIDTH
-      : 0,
+  const availableWidth = createMemo(
+    () =>
+      dimensions().width -
+      (config.tabs?.enabled && config.tabs.vertical && sessionTabsFitVertically(dimensions().width)
+        ? SESSION_SIDEBAR_WIDTH
+        : 0),
   )
-  const wide = createMemo(() => dimensions().width - tabRailWidth() > 120)
+  const wide = createMemo(() => availableWidth() > 120)
   const sidebarVisible = createMemo(() => {
     if (session()?.parentID) return false
     if (sidebarOpen()) return true
     if (sidebar() === "auto" && wide()) return true
     return false
   })
-  const contentWidth = createMemo(() => dimensions().width - tabRailWidth() - (sidebarVisible() ? 42 : 0) - 4)
+  const contentWidth = createMemo(() => availableWidth() - (sidebarVisible() ? 42 : 0) - 4)
   const models = createMemo(() => data.location.model.list(location()) ?? [])
 
   const scrollAcceleration = createMemo(() => getScrollAcceleration(config))

+ 5 - 1
packages/tui/src/routes/session/sidebar.tsx

@@ -28,7 +28,11 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) {
         position={props.overlay ? "absolute" : "relative"}
       >
         <scrollbox
-          ref={(scroll) => queueMicrotask(() => scroll.verticalScrollBar.resetVisibilityControl())}
+          ref={(scroll) =>
+            queueMicrotask(() => {
+              if (!scroll.isDestroyed) scroll.verticalScrollBar.resetVisibilityControl()
+            })
+          }
           flexGrow={1}
           scrollAcceleration={scrollAcceleration()}
           verticalScrollbarOptions={{

+ 1 - 12
packages/tui/src/ui/dialog-prompt.tsx

@@ -1,7 +1,7 @@
 import { TextareaRenderable, TextAttributes } from "@opentui/core"
 import { Keymap } from "../context/keymap"
 import { useTheme } from "../context/theme"
-import { useDialog, type DialogContext } from "./dialog"
+import { useDialog } from "./dialog"
 import { Show, createEffect, createSignal, onMount, type JSX } from "solid-js"
 import { Spinner } from "../component/spinner"
 
@@ -112,14 +112,3 @@ export function DialogPrompt(props: DialogPromptProps) {
     </box>
   )
 }
-
-DialogPrompt.show = (dialog: DialogContext, title: string, options?: Omit<DialogPromptProps, "title">) => {
-  return new Promise<string | null>((resolve) => {
-    dialog.replace(
-      () => (
-        <DialogPrompt title={title} {...options} onConfirm={(value) => resolve(value)} onCancel={() => resolve(null)} />
-      ),
-      () => resolve(null),
-    )
-  })
-}