Skip to content
This repository was archived by the owner on Apr 1, 2026. It is now read-only.

Commit 4ab4baf

Browse files
feat(sidebar): add expandable sections for sidebar (anomalyco#4132)
Co-authored-by: GitHub Action <action@github.com>
1 parent 90f05eb commit 4ab4baf

1 file changed

Lines changed: 117 additions & 92 deletions

File tree

  • packages/opencode/src/cli/cmd/tui/routes/session

packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx

Lines changed: 117 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useSync } from "@tui/context/sync"
2-
import { createMemo, For, Show, Switch, Match } from "solid-js"
2+
import { createMemo, For, Show, Switch, Match, createSignal } from "solid-js"
33
import { useTheme } from "../../context/theme"
44
import { Locale } from "@/util/locale"
55
import path from "path"
@@ -13,6 +13,11 @@ export function Sidebar(props: { sessionID: string }) {
1313
const todo = createMemo(() => sync.data.todo[props.sessionID] ?? [])
1414
const messages = createMemo(() => sync.data.message[props.sessionID] ?? [])
1515

16+
const [mcpExpanded, setMcpExpanded] = createSignal(true)
17+
const [diffExpanded, setDiffExpanded] = createSignal(true)
18+
const [todoExpanded, setTodoExpanded] = createSignal(true)
19+
const [lspExpanded, setLspExpanded] = createSignal(true)
20+
1621
const cost = createMemo(() => {
1722
const total = messages().reduce((sum, x) => sum + (x.role === "assistant" ? x.cost : 0), 0)
1823
return new Intl.NumberFormat("en-US", {
@@ -55,110 +60,130 @@ export function Sidebar(props: { sessionID: string }) {
5560
</box>
5661
<Show when={Object.keys(sync.data.mcp).length > 0}>
5762
<box>
58-
<text fg={theme.text}>
59-
<b>MCP</b>
60-
</text>
61-
<For each={Object.entries(sync.data.mcp)}>
62-
{([key, item]) => (
63-
<box flexDirection="row" gap={1}>
64-
<text
65-
flexShrink={0}
66-
style={{
67-
fg: {
68-
connected: theme.success,
69-
failed: theme.error,
70-
disabled: theme.textMuted,
71-
}[item.status],
72-
}}
73-
>
74-
75-
</text>
76-
<text fg={theme.text} wrapMode="word">
77-
{key}{" "}
78-
<span style={{ fg: theme.textMuted }}>
79-
<Switch>
80-
<Match when={item.status === "connected"}>Connected</Match>
81-
<Match when={item.status === "failed" && item}>{(val) => <i>{val().error}</i>}</Match>
82-
<Match when={item.status === "disabled"}>Disabled in configuration</Match>
83-
</Switch>
84-
</span>
85-
</text>
86-
</box>
87-
)}
88-
</For>
63+
<box flexDirection="row" gap={1} onMouseDown={() => setMcpExpanded(!mcpExpanded())}>
64+
<text fg={theme.text}>{mcpExpanded() ? "▼" : "▶"}</text>
65+
<text fg={theme.text}>
66+
<b>MCP</b>
67+
</text>
68+
</box>
69+
<Show when={mcpExpanded()}>
70+
<For each={Object.entries(sync.data.mcp)}>
71+
{([key, item]) => (
72+
<box flexDirection="row" gap={1}>
73+
<text
74+
flexShrink={0}
75+
style={{
76+
fg: {
77+
connected: theme.success,
78+
failed: theme.error,
79+
disabled: theme.textMuted,
80+
}[item.status],
81+
}}
82+
>
83+
84+
</text>
85+
<text fg={theme.text} wrapMode="word">
86+
{key}{" "}
87+
<span style={{ fg: theme.textMuted }}>
88+
<Switch>
89+
<Match when={item.status === "connected"}>Connected</Match>
90+
<Match when={item.status === "failed" && item}>{(val) => <i>{val().error}</i>}</Match>
91+
<Match when={item.status === "disabled"}>Disabled in configuration</Match>
92+
</Switch>
93+
</span>
94+
</text>
95+
</box>
96+
)}
97+
</For>
98+
</Show>
8999
</box>
90100
</Show>
91101
<Show when={sync.data.lsp.length > 0}>
92102
<box>
93-
<text fg={theme.text}>
94-
<b>LSP</b>
95-
</text>
96-
<For each={sync.data.lsp}>
97-
{(item) => (
98-
<box flexDirection="row" gap={1}>
99-
<text
100-
flexShrink={0}
101-
style={{
102-
fg: {
103-
connected: theme.success,
104-
error: theme.error,
105-
}[item.status],
106-
}}
107-
>
108-
109-
</text>
110-
<text fg={theme.textMuted}>
111-
{item.id} {item.root}
112-
</text>
113-
</box>
114-
)}
115-
</For>
103+
<box flexDirection="row" gap={1} onMouseDown={() => setLspExpanded(!lspExpanded())}>
104+
<text fg={theme.text}>{lspExpanded() ? "▼" : "▶"}</text>
105+
<text fg={theme.text}>
106+
<b>LSP</b>
107+
</text>
108+
</box>
109+
<Show when={lspExpanded()}>
110+
<For each={sync.data.lsp}>
111+
{(item) => (
112+
<box flexDirection="row" gap={1}>
113+
<text
114+
flexShrink={0}
115+
style={{
116+
fg: {
117+
connected: theme.success,
118+
error: theme.error,
119+
}[item.status],
120+
}}
121+
>
122+
123+
</text>
124+
<text fg={theme.textMuted}>
125+
{item.id} {item.root}
126+
</text>
127+
</box>
128+
)}
129+
</For>
130+
</Show>
116131
</box>
117132
</Show>
118133
<Show when={todo().length > 0}>
119134
<box>
120-
<text fg={theme.text}>
121-
<b>Todo</b>
122-
</text>
123-
<For each={todo()}>
124-
{(todo) => (
125-
<text style={{ fg: todo.status === "in_progress" ? theme.success : theme.textMuted }}>
126-
[{todo.status === "completed" ? "✓" : " "}] {todo.content}
127-
</text>
128-
)}
129-
</For>
135+
<box flexDirection="row" gap={1} onMouseDown={() => setTodoExpanded(!todoExpanded())}>
136+
<text fg={theme.text}>{todoExpanded() ? "▼" : "▶"}</text>
137+
<text fg={theme.text}>
138+
<b>Todo</b>
139+
</text>
140+
</box>
141+
<Show when={todoExpanded()}>
142+
<For each={todo()}>
143+
{(todo) => (
144+
<text style={{ fg: todo.status === "in_progress" ? theme.success : theme.textMuted }}>
145+
[{todo.status === "completed" ? "✓" : " "}] {todo.content}
146+
</text>
147+
)}
148+
</For>
149+
</Show>
130150
</box>
131151
</Show>
132152
<Show when={diff().length > 0}>
133153
<box>
134-
<text fg={theme.text}>
135-
<b>Modified Files</b>
136-
</text>
137-
<For each={diff() || []}>
138-
{(item) => {
139-
const file = createMemo(() => {
140-
const splits = item.file.split(path.sep).filter(Boolean)
141-
const last = splits.at(-1)!
142-
const rest = splits.slice(0, -1).join(path.sep)
143-
return Locale.truncateMiddle(rest, 30 - last.length) + "/" + last
144-
})
145-
return (
146-
<box flexDirection="row" gap={1} justifyContent="space-between">
147-
<text fg={theme.textMuted} wrapMode="char">
148-
{file()}
149-
</text>
150-
<box flexDirection="row" gap={1} flexShrink={0}>
151-
<Show when={item.additions}>
152-
<text fg={theme.diffAdded}>+{item.additions}</text>
153-
</Show>
154-
<Show when={item.deletions}>
155-
<text fg={theme.diffRemoved}>-{item.deletions}</text>
156-
</Show>
154+
<box flexDirection="row" gap={1} onMouseDown={() => setDiffExpanded(!diffExpanded())}>
155+
<text fg={theme.text}>{diffExpanded() ? "▼" : "▶"}</text>
156+
<text fg={theme.text}>
157+
<b>Modified Files</b>
158+
</text>
159+
</box>
160+
<Show when={diffExpanded()}>
161+
<For each={diff() || []}>
162+
{(item) => {
163+
const file = createMemo(() => {
164+
const splits = item.file.split(path.sep).filter(Boolean)
165+
const last = splits.at(-1)!
166+
const rest = splits.slice(0, -1).join(path.sep)
167+
return Locale.truncateMiddle(rest, 30 - last.length) + "/" + last
168+
})
169+
return (
170+
<box flexDirection="row" gap={1} justifyContent="space-between">
171+
<text fg={theme.textMuted} wrapMode="char">
172+
{file()}
173+
</text>
174+
<box flexDirection="row" gap={1} flexShrink={0}>
175+
<Show when={item.additions}>
176+
<text fg={theme.diffAdded}>+{item.additions}</text>
177+
</Show>
178+
<Show when={item.deletions}>
179+
<text fg={theme.diffRemoved}>-{item.deletions}</text>
180+
</Show>
181+
</box>
157182
</box>
158-
</box>
159-
)
160-
}}
161-
</For>
183+
)
184+
}}
185+
</For>
186+
</Show>
162187
</box>
163188
</Show>
164189
</box>

0 commit comments

Comments
 (0)