Skip to content
Merged
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
2 changes: 0 additions & 2 deletions packages/tui/src/component/dialog-status.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
20 changes: 9 additions & 11 deletions packages/tui/src/component/prompt/autocomplete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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() {
Expand Down
3 changes: 0 additions & 3 deletions packages/tui/src/component/prompt/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
12 changes: 4 additions & 8 deletions packages/tui/src/component/session-tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 }>()
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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()}
Expand Down Expand Up @@ -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()}
Expand Down
2 changes: 0 additions & 2 deletions packages/tui/src/context/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 }>
Expand Down
22 changes: 9 additions & 13 deletions packages/tui/src/feature-plugins/prompt/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
Expand All @@ -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]
Expand Down
5 changes: 1 addition & 4 deletions packages/tui/src/mini/runtime.queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
14 changes: 8 additions & 6 deletions packages/tui/src/routes/session/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
6 changes: 5 additions & 1 deletion packages/tui/src/routes/session/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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={{
Expand Down
13 changes: 1 addition & 12 deletions packages/tui/src/ui/dialog-prompt.tsx
Original file line number Diff line number Diff line change
@@ -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"

Expand Down Expand Up @@ -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),
)
})
}
Loading