diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index d5298518700..566778bb0ed 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -71,13 +71,13 @@ import { DialogExportOptions } from "../../ui/dialog-export-options" addDefaultParsers(parsers.parsers) class CustomSpeedScroll implements ScrollAcceleration { - constructor(private speed: number) {} + constructor(private speed: number) { } tick(_now?: number): number { return this.speed } - reset(): void {} + reset(): void { } } const context = createContext<{ @@ -119,6 +119,16 @@ export function Session() { const dimensions = useTerminalDimensions() const [sidebar, setSidebar] = createSignal<"show" | "hide" | "auto">(kv.get("sidebar", "auto")) + + const hw = 1 + const min = 20 + const max = 80 + + function clamp(n: number) { + return Math.max(min, Math.min(max, n)) + } + + const [w, setW] = createSignal(clamp(kv.get("sidebar_width", 42))) const [conceal, setConceal] = createSignal(true) const [showThinking, setShowThinking] = createSignal(kv.get("thinking_visibility", true)) const [showTimestamps, setShowTimestamps] = createSignal(kv.get("timestamps", "hide") === "show") @@ -129,6 +139,32 @@ export function Session() { const [diffWrapMode, setDiffWrapMode] = createSignal<"word" | "none">("word") const [animationsEnabled, setAnimationsEnabled] = createSignal(kv.get("animations_enabled", true)) + const [drag, setDrag] = createSignal(false) + const [sx, setSx] = createSignal(0) + const [sw, setSw] = createSignal(0) + const [hov, setHov] = createSignal(false) + + function save() { + kv.set("sidebar_width", w()) + } + + function down(x: number) { + setDrag(true) + setSx(x) + setSw(w()) + } + + function move(x: number) { + if (!drag()) return + setW(clamp(sw() + (sx() - x))) + } + + function up() { + if (!drag()) return + setDrag(false) + save() + } + const wide = createMemo(() => dimensions().width > 120) const sidebarVisible = createMemo(() => { if (session()?.parentID) return false @@ -136,7 +172,9 @@ export function Session() { if (sidebar() === "auto" && wide()) return true return false }) - const contentWidth = createMemo(() => dimensions().width - (sidebarVisible() ? 42 : 0) - 4) + const contentWidth = createMemo(() => dimensions().width - (sidebarVisible() ? w() + hw : 0) - 4) + + createEffect(() => !sidebarVisible() && setDrag(false)) const scrollAcceleration = createMemo(() => { const tui = sync.data.config.tui @@ -300,29 +338,29 @@ export function Session() { command.register(() => [ ...(sync.data.config.share !== "disabled" ? [ - { - title: "Share session", - value: "session.share", - suggested: route.type === "session", - keybind: "session_share" as const, - disabled: !!session()?.share?.url, - category: "Session", - onSelect: async (dialog: any) => { - await sdk.client.session - .share({ - sessionID: route.sessionID, - }) - .then((res) => - Clipboard.copy(res.data!.share!.url).catch(() => - toast.show({ message: "Failed to copy URL to clipboard", variant: "error" }), - ), - ) - .then(() => toast.show({ message: "Share URL copied to clipboard!", variant: "success" })) - .catch(() => toast.show({ message: "Failed to share session", variant: "error" })) - dialog.clear() - }, + { + title: "Share session", + value: "session.share", + suggested: route.type === "session", + keybind: "session_share" as const, + disabled: !!session()?.share?.url, + category: "Session", + onSelect: async (dialog: any) => { + await sdk.client.session + .share({ + sessionID: route.sessionID, + }) + .then((res) => + Clipboard.copy(res.data!.share!.url).catch(() => + toast.show({ message: "Failed to copy URL to clipboard", variant: "error" }), + ), + ) + .then(() => toast.show({ message: "Share URL copied to clipboard!", variant: "success" })) + .catch(() => toast.show({ message: "Failed to share session", variant: "error" })) + dialog.clear() }, - ] + }, + ] : []), { title: "Rename session", @@ -418,7 +456,7 @@ export function Session() { category: "Session", onSelect: async (dialog) => { const status = sync.data.session_status?.[route.sessionID] - if (status?.type !== "idle") await sdk.client.session.abort({ sessionID: route.sessionID }).catch(() => {}) + if (status?.type !== "idle") await sdk.client.session.abort({ sessionID: route.sessionID }).catch(() => { }) const revert = session().revert?.messageID const message = messages().findLast((x) => (!revert || x.id < revert) && x.role === "user") if (!message) return @@ -1000,7 +1038,18 @@ export function Session() { sync, }} > - + { + move(e.x) + }} + onMouseUp={() => { + up() + }} + onMouseDragEnd={() => { + up() + }} + > @@ -1140,7 +1189,25 @@ export function Session() { - + <> + { + setHov(true) + }} + onMouseOut={() => { + setHov(false) + }} + onMouseDown={(e) => { + e.preventDefault() + down(e.x) + }} + /> + + @@ -1425,19 +1492,19 @@ function ToolPart(props: { last: boolean; part: ToolPart; message: AssistantMess const style: BoxProps = container === "block" || permission ? { - border: permissionIndex === 0 ? (["left", "right"] as const) : (["left"] as const), - paddingTop: 1, - paddingBottom: 1, - paddingLeft: 2, - marginTop: 1, - gap: 1, - backgroundColor: theme.backgroundPanel, - customBorderChars: SplitBorder.customBorderChars, - borderColor: permissionIndex === 0 ? theme.warning : theme.background, - } + border: permissionIndex === 0 ? (["left", "right"] as const) : (["left"] as const), + paddingTop: 1, + paddingBottom: 1, + paddingLeft: 2, + marginTop: 1, + gap: 1, + backgroundColor: theme.backgroundPanel, + customBorderChars: SplitBorder.customBorderChars, + borderColor: permissionIndex === 0 ? theme.warning : theme.background, + } : { - paddingLeft: 3, - } + paddingLeft: 3, + } return ( sync.session.get(props.sessionID)!) @@ -72,7 +72,7 @@ export function Sidebar(props: { sessionID: string }) {