diff options
| author | OpeOginni <[email protected]> | 2025-11-12 23:15:17 +0100 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-11-12 16:15:17 -0600 |
| commit | 4ab4baf3a4a5f11e88462480e600053b1bd953ed (patch) | |
| tree | 28e08641a5fd8dc865b150736503428ac8970c65 | |
| parent | 90f05eb9c23ddd37a4337e22429741f87dc725cd (diff) | |
| download | opencode-4ab4baf3a4a5f11e88462480e600053b1bd953ed.tar.gz opencode-4ab4baf3a4a5f11e88462480e600053b1bd953ed.zip | |
feat(sidebar): add expandable sections for sidebar (#4132)
Co-authored-by: GitHub Action <[email protected]>
| -rw-r--r-- | packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx | 209 |
1 files changed, 117 insertions, 92 deletions
diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx index 8374f701c..ee83a3afc 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx @@ -1,5 +1,5 @@ import { useSync } from "@tui/context/sync" -import { createMemo, For, Show, Switch, Match } from "solid-js" +import { createMemo, For, Show, Switch, Match, createSignal } from "solid-js" import { useTheme } from "../../context/theme" import { Locale } from "@/util/locale" import path from "path" @@ -13,6 +13,11 @@ export function Sidebar(props: { sessionID: string }) { const todo = createMemo(() => sync.data.todo[props.sessionID] ?? []) const messages = createMemo(() => sync.data.message[props.sessionID] ?? []) + const [mcpExpanded, setMcpExpanded] = createSignal(true) + const [diffExpanded, setDiffExpanded] = createSignal(true) + const [todoExpanded, setTodoExpanded] = createSignal(true) + const [lspExpanded, setLspExpanded] = createSignal(true) + const cost = createMemo(() => { const total = messages().reduce((sum, x) => sum + (x.role === "assistant" ? x.cost : 0), 0) return new Intl.NumberFormat("en-US", { @@ -55,110 +60,130 @@ export function Sidebar(props: { sessionID: string }) { </box> <Show when={Object.keys(sync.data.mcp).length > 0}> <box> - <text fg={theme.text}> - <b>MCP</b> - </text> - <For each={Object.entries(sync.data.mcp)}> - {([key, item]) => ( - <box flexDirection="row" gap={1}> - <text - flexShrink={0} - style={{ - fg: { - connected: theme.success, - failed: theme.error, - disabled: theme.textMuted, - }[item.status], - }} - > - • - </text> - <text fg={theme.text} wrapMode="word"> - {key}{" "} - <span style={{ fg: theme.textMuted }}> - <Switch> - <Match when={item.status === "connected"}>Connected</Match> - <Match when={item.status === "failed" && item}>{(val) => <i>{val().error}</i>}</Match> - <Match when={item.status === "disabled"}>Disabled in configuration</Match> - </Switch> - </span> - </text> - </box> - )} - </For> + <box flexDirection="row" gap={1} onMouseDown={() => setMcpExpanded(!mcpExpanded())}> + <text fg={theme.text}>{mcpExpanded() ? "▼" : "▶"}</text> + <text fg={theme.text}> + <b>MCP</b> + </text> + </box> + <Show when={mcpExpanded()}> + <For each={Object.entries(sync.data.mcp)}> + {([key, item]) => ( + <box flexDirection="row" gap={1}> + <text + flexShrink={0} + style={{ + fg: { + connected: theme.success, + failed: theme.error, + disabled: theme.textMuted, + }[item.status], + }} + > + • + </text> + <text fg={theme.text} wrapMode="word"> + {key}{" "} + <span style={{ fg: theme.textMuted }}> + <Switch> + <Match when={item.status === "connected"}>Connected</Match> + <Match when={item.status === "failed" && item}>{(val) => <i>{val().error}</i>}</Match> + <Match when={item.status === "disabled"}>Disabled in configuration</Match> + </Switch> + </span> + </text> + </box> + )} + </For> + </Show> </box> </Show> <Show when={sync.data.lsp.length > 0}> <box> - <text fg={theme.text}> - <b>LSP</b> - </text> - <For each={sync.data.lsp}> - {(item) => ( - <box flexDirection="row" gap={1}> - <text - flexShrink={0} - style={{ - fg: { - connected: theme.success, - error: theme.error, - }[item.status], - }} - > - • - </text> - <text fg={theme.textMuted}> - {item.id} {item.root} - </text> - </box> - )} - </For> + <box flexDirection="row" gap={1} onMouseDown={() => setLspExpanded(!lspExpanded())}> + <text fg={theme.text}>{lspExpanded() ? "▼" : "▶"}</text> + <text fg={theme.text}> + <b>LSP</b> + </text> + </box> + <Show when={lspExpanded()}> + <For each={sync.data.lsp}> + {(item) => ( + <box flexDirection="row" gap={1}> + <text + flexShrink={0} + style={{ + fg: { + connected: theme.success, + error: theme.error, + }[item.status], + }} + > + • + </text> + <text fg={theme.textMuted}> + {item.id} {item.root} + </text> + </box> + )} + </For> + </Show> </box> </Show> <Show when={todo().length > 0}> <box> - <text fg={theme.text}> - <b>Todo</b> - </text> - <For each={todo()}> - {(todo) => ( - <text style={{ fg: todo.status === "in_progress" ? theme.success : theme.textMuted }}> - [{todo.status === "completed" ? "✓" : " "}] {todo.content} - </text> - )} - </For> + <box flexDirection="row" gap={1} onMouseDown={() => setTodoExpanded(!todoExpanded())}> + <text fg={theme.text}>{todoExpanded() ? "▼" : "▶"}</text> + <text fg={theme.text}> + <b>Todo</b> + </text> + </box> + <Show when={todoExpanded()}> + <For each={todo()}> + {(todo) => ( + <text style={{ fg: todo.status === "in_progress" ? theme.success : theme.textMuted }}> + [{todo.status === "completed" ? "✓" : " "}] {todo.content} + </text> + )} + </For> + </Show> </box> </Show> <Show when={diff().length > 0}> <box> - <text fg={theme.text}> - <b>Modified Files</b> - </text> - <For each={diff() || []}> - {(item) => { - const file = createMemo(() => { - const splits = item.file.split(path.sep).filter(Boolean) - const last = splits.at(-1)! - const rest = splits.slice(0, -1).join(path.sep) - return Locale.truncateMiddle(rest, 30 - last.length) + "/" + last - }) - return ( - <box flexDirection="row" gap={1} justifyContent="space-between"> - <text fg={theme.textMuted} wrapMode="char"> - {file()} - </text> - <box flexDirection="row" gap={1} flexShrink={0}> - <Show when={item.additions}> - <text fg={theme.diffAdded}>+{item.additions}</text> - </Show> - <Show when={item.deletions}> - <text fg={theme.diffRemoved}>-{item.deletions}</text> - </Show> + <box flexDirection="row" gap={1} onMouseDown={() => setDiffExpanded(!diffExpanded())}> + <text fg={theme.text}>{diffExpanded() ? "▼" : "▶"}</text> + <text fg={theme.text}> + <b>Modified Files</b> + </text> + </box> + <Show when={diffExpanded()}> + <For each={diff() || []}> + {(item) => { + const file = createMemo(() => { + const splits = item.file.split(path.sep).filter(Boolean) + const last = splits.at(-1)! + const rest = splits.slice(0, -1).join(path.sep) + return Locale.truncateMiddle(rest, 30 - last.length) + "/" + last + }) + return ( + <box flexDirection="row" gap={1} justifyContent="space-between"> + <text fg={theme.textMuted} wrapMode="char"> + {file()} + </text> + <box flexDirection="row" gap={1} flexShrink={0}> + <Show when={item.additions}> + <text fg={theme.diffAdded}>+{item.additions}</text> + </Show> + <Show when={item.deletions}> + <text fg={theme.diffRemoved}>-{item.deletions}</text> + </Show> + </box> </box> - </box> - ) - }} - </For> + ) + }} + </For> + </Show> </box> </Show> </box> |
