Skip to content

Commit e78bcb0

Browse files
committed
feat: add toggle for session section collapse
1 parent 4e4ea76 commit e78bcb0

6 files changed

Lines changed: 102 additions & 17 deletions

File tree

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package com.ashotn.opencode.relay.actions
2+
3+
import com.ashotn.opencode.relay.settings.OpenCodeSettings
4+
import com.ashotn.opencode.relay.settings.OpenCodeSettingsChangedListener
5+
import com.ashotn.opencode.relay.settings.snapshot
6+
import com.intellij.openapi.actionSystem.ActionUpdateThread
7+
import com.intellij.openapi.actionSystem.AnActionEvent
8+
import com.intellij.openapi.actionSystem.ToggleAction
9+
import com.intellij.openapi.project.Project
10+
11+
class ToggleSessionsSectionAction(private val project: Project) : ToggleAction("Sessions", "Show or hide the sessions section above the inline terminal", null) {
12+
13+
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
14+
15+
override fun update(e: AnActionEvent) {
16+
super.update(e)
17+
val settings = OpenCodeSettings.getInstance(project)
18+
e.presentation.isEnabled = settings.inlineTerminalEnabled
19+
e.presentation.description = if (settings.inlineTerminalEnabled) {
20+
if (settings.sessionsSectionVisible) {
21+
"Hide the sessions section and let the inline terminal use the full height"
22+
} else {
23+
"Show the sessions section above the inline terminal"
24+
}
25+
} else {
26+
"Enable the inline terminal in settings to toggle the sessions section"
27+
}
28+
}
29+
30+
override fun isSelected(e: AnActionEvent): Boolean = OpenCodeSettings.getInstance(project).sessionsSectionVisible
31+
32+
override fun setSelected(e: AnActionEvent, state: Boolean) {
33+
updateSessionsSectionVisibility(project, state)
34+
}
35+
36+
companion object {
37+
fun updateSessionsSectionVisibility(project: Project, visible: Boolean) {
38+
val settings = OpenCodeSettings.getInstance(project)
39+
if (settings.sessionsSectionVisible == visible) return
40+
41+
val oldSettings = settings.snapshot()
42+
settings.sessionsSectionVisible = visible
43+
val newSettings = settings.snapshot()
44+
project.messageBus.syncPublisher(OpenCodeSettingsChangedListener.TOPIC)
45+
.onSettingsChanged(oldSettings, newSettings)
46+
}
47+
}
48+
}

src/main/kotlin/com/ashotn/opencode/relay/settings/OpenCodeSettings.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class OpenCodeSettings : PersistentStateComponent<OpenCodeSettings.State> {
2424
var diffTraceEnabled: Boolean = false,
2525
var diffTraceHistoryEnabled: Boolean = false,
2626
var inlineTerminalEnabled: Boolean = true,
27+
var sessionsSectionVisible: Boolean = true,
2728
var terminalEngine: TerminalEngine = TerminalEngine.CLASSIC,
2829
)
2930

@@ -59,6 +60,10 @@ class OpenCodeSettings : PersistentStateComponent<OpenCodeSettings.State> {
5960
get() = state.inlineTerminalEnabled
6061
set(value) { state.inlineTerminalEnabled = value }
6162

63+
var sessionsSectionVisible: Boolean
64+
get() = state.sessionsSectionVisible
65+
set(value) { state.sessionsSectionVisible = value }
66+
6267
var terminalEngine: TerminalEngine
6368
get() = state.terminalEngine
6469
set(value) { state.terminalEngine = value }
@@ -68,3 +73,14 @@ class OpenCodeSettings : PersistentStateComponent<OpenCodeSettings.State> {
6873
project.getService(OpenCodeSettings::class.java)
6974
}
7075
}
76+
77+
fun OpenCodeSettings.snapshot(): OpenCodeSettingsSnapshot = OpenCodeSettingsSnapshot(
78+
serverPort = serverPort,
79+
executablePath = executablePath,
80+
inlineDiffEnabled = inlineDiffEnabled,
81+
diffTraceEnabled = diffTraceEnabled,
82+
diffTraceHistoryEnabled = diffTraceHistoryEnabled,
83+
inlineTerminalEnabled = inlineTerminalEnabled,
84+
sessionsSectionVisible = sessionsSectionVisible,
85+
terminalEngine = terminalEngine,
86+
)

src/main/kotlin/com/ashotn/opencode/relay/settings/OpenCodeSettingsChangedListener.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ data class OpenCodeSettingsSnapshot(
99
val diffTraceEnabled: Boolean,
1010
val diffTraceHistoryEnabled: Boolean,
1111
val inlineTerminalEnabled: Boolean,
12+
val sessionsSectionVisible: Boolean,
1213
val terminalEngine: OpenCodeSettings.TerminalEngine,
1314
)
1415

src/main/kotlin/com/ashotn/opencode/relay/settings/OpenCodeSettingsConfigurable.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ class OpenCodeSettingsConfigurable(private val project: Project) :
212212
pendingState.diffTraceEnabled = settings.diffTraceEnabled
213213
pendingState.diffTraceHistoryEnabled = settings.diffTraceHistoryEnabled
214214
pendingState.inlineTerminalEnabled = settings.inlineTerminalEnabled
215+
pendingState.sessionsSectionVisible = settings.sessionsSectionVisible
215216
pendingState.terminalEngine =
216217
if (settings.terminalEngine == TerminalEngine.REWORKED) TerminalEngine.CLASSIC else settings.terminalEngine
217218
}
@@ -227,6 +228,7 @@ class OpenCodeSettingsConfigurable(private val project: Project) :
227228
diffTraceEnabled = state.diffTraceEnabled,
228229
diffTraceHistoryEnabled = state.diffTraceHistoryEnabled,
229230
inlineTerminalEnabled = state.inlineTerminalEnabled,
231+
sessionsSectionVisible = state.sessionsSectionVisible,
230232
terminalEngine = state.terminalEngine,
231233
)
232234
}

src/main/kotlin/com/ashotn/opencode/relay/toolwindow/OpenCodeToolWindowFactory.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import com.ashotn.opencode.relay.actions.OpenSettingsAction
1010
import com.ashotn.opencode.relay.actions.OpenTerminalAction
1111
import com.ashotn.opencode.relay.actions.ResetPluginAction
1212
import com.ashotn.opencode.relay.actions.StopServerAction
13+
import com.ashotn.opencode.relay.actions.ToggleSessionsSectionAction
1314
import com.intellij.ide.ActivityTracker
1415
import com.intellij.openapi.application.ApplicationManager
1516
import com.intellij.openapi.project.Project
@@ -23,7 +24,7 @@ class OpenCodeToolWindowFactory : ToolWindowFactory {
2324
val panel = OpenCodeToolWindowPanel(project)
2425
val content = ContentFactory.getInstance().createContent(panel, "", false)
2526
toolWindow.contentManager.addContent(content)
26-
toolWindow.setTitleActions(listOf(McpServersAction(project), OpenTerminalAction(project), OpenBrowserAction(project), StopServerAction(project), ResetPluginAction(project), OpenSettingsAction(project)))
27+
toolWindow.setTitleActions(listOf(ToggleSessionsSectionAction(project), McpServersAction(project), OpenTerminalAction(project), OpenBrowserAction(project), StopServerAction(project), ResetPluginAction(project), OpenSettingsAction(project)))
2728

2829
// Update tool window icon based on server connection state
2930
val plugin = OpenCodePlugin.getInstance(project)

src/main/kotlin/com/ashotn/opencode/relay/toolwindow/OpenCodeToolWindowPanel.kt

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,12 @@ class OpenCodeToolWindowPanel(private val project: Project) : JPanel(BorderLayou
5757
private val outerCardPanel = JPanel(outerCardLayout)
5858
private val pendingFilesPanel = PendingFilesPanel(project, this)
5959
private var tuiPanel: TuiPanel = createTuiPanel()
60-
private var activeTuiEngine: TerminalEngine = effectiveTerminalEngine(OpenCodeSettings.getInstance(project).terminalEngine)
60+
private var activeTuiEngine: TerminalEngine =
61+
effectiveTerminalEngine(OpenCodeSettings.getInstance(project).terminalEngine)
6162
private val syncScheduled = AtomicBoolean(false)
6263
private val plugin = OpenCodePlugin.getInstance(project)
6364
private val serverStateListener = ServerStateListener { requestSyncCard() }
65+
private var expandedDividerLocation: Int? = null
6466

6567
// Split pane that stacks content (top) and TUI (bottom).
6668
// The TUI half is hidden until the server is READY.
@@ -136,34 +138,29 @@ class OpenCodeToolWindowPanel(private val project: Project) : JPanel(BorderLayou
136138
}
137139

138140
private fun syncCard() {
141+
val settings = OpenCodeSettings.getInstance(project)
139142
val serverReady = plugin.serverState == ServerState.READY
143+
val inlineTerminal = serverReady && settings.inlineTerminalEnabled
140144

141-
// Show / hide TUI panel based on server state and inline terminal setting.
142-
val inlineTerminal = serverReady && OpenCodeSettings.getInstance(project).inlineTerminalEnabled
143145
if (inlineTerminal) {
144146
tuiPanel.startIfNeeded()
145147
if (tuiPanel.isStarted) {
146-
// Restore the split whenever the divider is hidden (e.g. after a reset).
147-
// Run after layout so the panel has a real height.
148-
if (splitPane.dividerSize == 0) {
149-
ApplicationManager.getApplication().invokeLater {
150-
val total = splitPane.height
151-
if (total > 0) {
152-
splitPane.dividerSize = JBUI.scale(2)
153-
splitPane.dividerLocation = (total * 0.25).toInt()
154-
}
148+
if (!settings.sessionsSectionVisible) {
149+
if (splitPane.dividerSize > 0) {
150+
expandedDividerLocation = splitPane.dividerLocation
155151
}
152+
collapseSplit(showTopSection = false, dividerLocation = 0)
153+
} else {
154+
restoreSplitView()
156155
}
157156
} else {
158-
splitPane.dividerSize = 0
159-
splitPane.dividerLocation = Int.MAX_VALUE
157+
collapseSplit(showTopSection = true)
160158
}
161159
} else {
162160
// Inline terminal disabled or server not running – stop any running widget
163161
// so it doesn't linger in the background, then collapse the divider.
164162
if (tuiPanel.isStarted) tuiPanel.stop()
165-
splitPane.dividerSize = 0
166-
splitPane.dividerLocation = Int.MAX_VALUE
163+
collapseSplit(showTopSection = true)
167164
}
168165

169166
val showPending = when (plugin.serverState) {
@@ -182,6 +179,26 @@ class OpenCodeToolWindowPanel(private val project: Project) : JPanel(BorderLayou
182179
outerCardLayout.show(outerCardPanel, if (showPending) CARD_PENDING else CARD_CONTENT)
183180
}
184181

182+
private fun collapseSplit(showTopSection: Boolean, dividerLocation: Int = Int.MAX_VALUE) {
183+
outerCardPanel.isVisible = showTopSection
184+
splitPane.dividerSize = 0
185+
splitPane.dividerLocation = dividerLocation
186+
}
187+
188+
private fun restoreSplitView() {
189+
outerCardPanel.isVisible = true
190+
if (splitPane.dividerSize == 0) {
191+
ApplicationManager.getApplication().invokeLater {
192+
val splitPaneHeight = splitPane.height
193+
if (splitPaneHeight > 1 && !project.isDisposed) {
194+
splitPane.dividerSize = JBUI.scale(2)
195+
splitPane.dividerLocation = (expandedDividerLocation ?: (splitPaneHeight * 0.25).toInt())
196+
.coerceIn(1, splitPaneHeight - 1)
197+
}
198+
}
199+
}
200+
}
201+
185202
fun focusTerminal() {
186203
tuiPanel.focusTerminal()
187204
}

0 commit comments

Comments
 (0)