Skip to content

Commit 4e4ea76

Browse files
committed
fix: 2026.1 prevent ESC key from stealing focus away from inline terminal
1 parent 56016d6 commit 4e4ea76

1 file changed

Lines changed: 29 additions & 16 deletions

File tree

src/main/kotlin/com/ashotn/opencode/relay/terminal/ClassicTuiPanel.kt

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,21 @@ import com.ashotn.opencode.relay.OpenCodePlugin
44
import com.ashotn.opencode.relay.OpenCodeProcessEnvironment
55
import com.ashotn.opencode.relay.settings.OpenCodeSettings
66
import com.ashotn.opencode.relay.util.serverUrl
7+
import com.intellij.ide.DataManager
78
import com.intellij.openapi.Disposable
9+
import com.intellij.openapi.actionSystem.CustomizedDataContext
10+
import com.intellij.openapi.actionSystem.PlatformDataKeys
811
import com.intellij.openapi.application.ApplicationManager
912
import com.intellij.openapi.diagnostic.Logger
1013
import com.intellij.openapi.project.Project
1114
import com.intellij.openapi.ui.ComponentContainer
1215
import com.intellij.openapi.util.Disposer
16+
import com.intellij.terminal.JBTerminalPanel
1317
import com.intellij.terminal.ui.TerminalWidget
1418
import org.jetbrains.plugins.terminal.LocalTerminalDirectRunner
1519
import org.jetbrains.plugins.terminal.ShellStartupOptions
1620
import org.jetbrains.plugins.terminal.ShellTerminalWidget
1721
import java.awt.BorderLayout
18-
import java.awt.event.KeyEvent
19-
import java.awt.event.KeyListener
2022
import java.lang.reflect.Proxy
2123
import javax.swing.JPanel
2224

@@ -37,6 +39,7 @@ class ClassicTuiPanel(
3739
) : JPanel(BorderLayout()), TuiPanel, Disposable {
3840

3941
private var terminalWidget: TerminalWidget? = null
42+
private var terminalPanel: JBTerminalPanel? = null
4043

4144
init {
4245
Disposer.register(parentDisposable, this)
@@ -84,12 +87,16 @@ class ClassicTuiPanel(
8487
val widget = runner.startShellTerminalWidget(this, startupOptions, true)
8588
terminalWidget = widget
8689
Disposer.register(this, widget)
90+
terminalPanel = ShellTerminalWidget.asShellJediTermWidget(widget)?.terminalPanel?.also { panel ->
91+
installEmbeddedTerminalDataProvider(panel)
92+
}
8793

8894
// When the shell process exits, clean up and notify the owner.
8995
widget.addTerminationCallback({
9096
ApplicationManager.getApplication().invokeLater {
9197
logger.debug("Classic terminal process terminated")
9298
if (terminalWidget === widget) {
99+
uninstallEmbeddedTerminalDataProvider()
93100
terminalWidget = null
94101
remove(widget.component)
95102
revalidate()
@@ -101,19 +108,6 @@ class ClassicTuiPanel(
101108

102109
add(widget.component, BorderLayout.CENTER)
103110

104-
// Consume ESC key to prevent IDE from stealing focus from the inline terminal.
105-
widget.component.addKeyListener(object : KeyListener {
106-
override fun keyPressed(e: KeyEvent) {
107-
if (e.keyCode == KeyEvent.VK_ESCAPE) {
108-
e.consume()
109-
}
110-
}
111-
112-
override fun keyTyped(e: KeyEvent) {}
113-
114-
override fun keyReleased(e: KeyEvent) {}
115-
})
116-
117111
revalidate()
118112
repaint()
119113

@@ -127,7 +121,7 @@ class ClassicTuiPanel(
127121
}
128122

129123
override fun focusTerminal() {
130-
terminalWidget?.component?.requestFocusInWindow()
124+
terminalWidget?.preferredFocusableComponent?.requestFocusInWindow()
131125
}
132126

133127
override val isStarted: Boolean get() = terminalWidget != null
@@ -136,6 +130,7 @@ class ClassicTuiPanel(
136130

137131
private fun tearDown() {
138132
val widget = terminalWidget ?: return
133+
uninstallEmbeddedTerminalDataProvider()
139134
terminalWidget = null
140135
remove(widget.component)
141136
revalidate()
@@ -150,6 +145,24 @@ class ClassicTuiPanel(
150145

151146
override fun dispose() = tearDown()
152147

148+
private fun installEmbeddedTerminalDataProvider(panel: JBTerminalPanel) {
149+
// JBTerminalPanel's internal TerminalEscapeKeyListener moves focus back to the editor
150+
// whenever the terminal reports a ToolWindow in its data context. Our terminal is embedded
151+
// inside a custom tool window, so return an explicit null for TOOL_WINDOW to keep ESC inside
152+
// the TUI while still allowing the key event to reach the terminal process.
153+
DataManager.registerDataProvider(panel) { dataId ->
154+
when {
155+
PlatformDataKeys.TOOL_WINDOW.`is`(dataId) -> CustomizedDataContext.EXPLICIT_NULL
156+
else -> null
157+
}
158+
}
159+
}
160+
161+
private fun uninstallEmbeddedTerminalDataProvider() {
162+
terminalPanel?.let(DataManager::removeDataProvider)
163+
terminalPanel = null
164+
}
165+
153166
companion object {
154167
private val logger = Logger.getInstance(ClassicTuiPanel::class.java)
155168

0 commit comments

Comments
 (0)