[terminal] IJPL-182032 Keep the cursor visible on the screen when the port forwarding panel appears

Previously, when the port forwarding panel appeared on top, the editor was scrolled down, so the cursor position was hidden.
Let's react on port forwarding panel size changes and update the scroll position.
Move top panel management from `ReworkedTerminalWidget` to the `ReworkedTerminalView`. Refactor the TerminalPanel to extract the layered pane logic to the separate `TerminalLayeredPane`.

(cherry picked from commit 78992a106db33e5861d362d5aca00942dbf0382c)

IJ-CR-159801

GitOrigin-RevId: 7bb54ad4cdb89c7f8765b06d340ed3b6eec0a5a4
This commit is contained in:
Konstantin Hudyakov
2025-04-08 12:26:43 +03:00
committed by intellij-monorepo-bot
parent a2a9631b60
commit df7a682006
2 changed files with 85 additions and 33 deletions

View File

@@ -13,7 +13,6 @@ import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.EditorFactory
import com.intellij.openapi.editor.ex.EditorEx
import com.intellij.openapi.editor.ex.util.EditorUtil
import com.intellij.openapi.editor.impl.DocumentImpl
import com.intellij.openapi.editor.impl.EditorImpl
import com.intellij.openapi.editor.impl.SoftWrapModelImpl
@@ -27,6 +26,7 @@ import com.intellij.terminal.JBTerminalSystemSettingsProviderBase
import com.intellij.terminal.session.TerminalSession
import com.intellij.ui.components.JBLayeredPane
import com.intellij.util.asDisposable
import com.intellij.util.ui.components.BorderLayoutPanel
import com.jediterm.core.util.TermSize
import com.jediterm.terminal.TtyConnector
import kotlinx.coroutines.*
@@ -66,6 +66,7 @@ internal class ReworkedTerminalView(
private val outputEditor: EditorEx
private val alternateBufferEditor: EditorEx
private val scrollingModel: TerminalOutputScrollingModel
private val terminalPanel: TerminalPanel
@@ -101,7 +102,7 @@ internal class ReworkedTerminalView(
outputEditor = createOutputEditor(settings, parentDisposable = this)
val outputModel = TerminalOutputModelImpl(outputEditor.document, maxOutputLength = TerminalUiUtils.getDefaultMaxOutputLength())
val scrollingModel = TerminalOutputScrollingModelImpl(outputEditor, outputModel, sessionModel, coroutineScope.childScope("TerminalOutputScrollingModel"))
scrollingModel = TerminalOutputScrollingModelImpl(outputEditor, outputModel, sessionModel, coroutineScope.childScope("TerminalOutputScrollingModel"))
outputEditor.putUserData(TerminalOutputScrollingModel.KEY, scrollingModel)
configureOutputEditor(
@@ -175,6 +176,23 @@ internal class ReworkedTerminalView(
return component.hasFocus()
}
fun setTopComponent(component: JComponent, disposable: Disposable) {
val resizeListener = object : ComponentAdapter() {
override fun componentResized(e: ComponentEvent) {
// Update scroll position on top component size change
// to always keep the cursor visible
scrollingModel.scrollToCursor(force = false)
}
}
component.addComponentListener(resizeListener)
terminalPanel.setTopComponent(component)
Disposer.register(disposable) {
component.removeComponentListener(resizeListener)
terminalPanel.remoteTopComponent(component)
}
}
private fun listenSearchController() {
terminalSearchController.addListener(object : TerminalSearchControllerListener {
override fun searchSessionStarted(session: TerminalSearchSession) {
@@ -349,9 +367,13 @@ internal class ReworkedTerminalView(
terminalPanel.addFocusListener(parentDisposable, listener)
}
private inner class TerminalPanel(initialContent: Editor) : JBLayeredPane(), UiDataProvider {
private inner class TerminalPanel(initialContent: Editor) : BorderLayoutPanel(), UiDataProvider {
private val layeredPane = TerminalLayeredPane(initialContent)
private var curEditor: Editor = initialContent
val preferredFocusableComponent: JComponent
get() = layeredPane.preferredFocusableComponent
private val delegatingFocusListener = object : FocusListener {
override fun focusGained(e: FocusEvent) {
focusListeners.forEach { it.focusGained(e) }
@@ -363,24 +385,8 @@ internal class ReworkedTerminalView(
}
init {
setTerminalContent(initialContent)
}
val preferredFocusableComponent: JComponent
get() = curEditor.contentComponent
fun setTerminalContent(editor: Editor) {
val prevEditor = curEditor
@Suppress("SENSELESS_COMPARISON") // called from init when curEditor == null
if (prevEditor != null) {
prevEditor.contentComponent.removeFocusListener(delegatingFocusListener)
remove(curEditor.component)
}
curEditor = editor
addToLayer(editor.component, DEFAULT_LAYER)
editor.contentComponent.addFocusListener(delegatingFocusListener)
revalidate()
repaint()
addToCenter(layeredPane)
updateFocusListeners(initialContent, initialContent)
}
override fun uiDataSnapshot(sink: DataSink) {
@@ -389,6 +395,61 @@ internal class ReworkedTerminalView(
sink[TerminalSearchController.KEY] = terminalSearchController
}
fun setTerminalContent(editor: Editor) {
layeredPane.setTerminalContent(editor)
updateFocusListeners(curEditor, editor)
curEditor = editor
}
fun installSearchComponent(component: SearchReplaceComponent) {
layeredPane.installSearchComponent(component)
}
fun removeSearchComponent(component: SearchReplaceComponent) {
layeredPane.removeSearchComponent(component)
}
fun setTopComponent(component: JComponent) {
addToTop(component)
revalidate()
repaint()
}
fun remoteTopComponent(component: JComponent) {
remove(component)
revalidate()
repaint()
}
private fun updateFocusListeners(prevEditor: Editor, newEditor: Editor) {
prevEditor.contentComponent.removeFocusListener(delegatingFocusListener)
newEditor.contentComponent.addFocusListener(delegatingFocusListener)
}
}
private class TerminalLayeredPane(initialContent: Editor) : JBLayeredPane() {
private var curEditor: Editor = initialContent
val preferredFocusableComponent: JComponent
get() = curEditor.contentComponent
init {
setTerminalContent(initialContent)
}
fun setTerminalContent(editor: Editor) {
val prevEditor = curEditor
@Suppress("SENSELESS_COMPARISON") // called from init when curEditor == null
if (prevEditor != null) {
remove(curEditor.component)
}
curEditor = editor
addToLayer(editor.component, DEFAULT_LAYER)
revalidate()
repaint()
}
fun installSearchComponent(component: SearchReplaceComponent) {
addToLayer(component, POPUP_LAYER)
revalidate()

View File

@@ -8,12 +8,10 @@ import com.intellij.terminal.TerminalTitle
import com.intellij.terminal.session.TerminalSession
import com.intellij.terminal.ui.TerminalWidget
import com.intellij.terminal.ui.TtyConnectorAccessor
import com.intellij.util.ui.components.BorderLayoutPanel
import com.jediterm.core.util.TermSize
import com.jediterm.terminal.TtyConnector
import org.jetbrains.annotations.Nls
import org.jetbrains.plugins.terminal.JBTerminalSystemSettingsProvider
import org.jetbrains.plugins.terminal.block.TerminalContentView
import java.util.concurrent.CompletableFuture
import javax.swing.JComponent
@@ -23,9 +21,7 @@ internal class ReworkedTerminalWidget(
parentDisposable: Disposable,
) : TerminalWidget {
private val sessionFuture = CompletableFuture<TerminalSession>()
private val view: TerminalContentView = ReworkedTerminalView(project, settings, sessionFuture)
private val panel: BorderLayoutPanel = BorderLayoutPanel()
private val view = ReworkedTerminalView(project, settings, sessionFuture)
override val terminalTitle: TerminalTitle = TerminalTitle()
@@ -40,8 +36,6 @@ internal class ReworkedTerminalWidget(
init {
Disposer.register(parentDisposable, this)
Disposer.register(this, view)
panel.addToCenter(view.component)
}
override fun connectToSession(session: TerminalSession) {
@@ -53,7 +47,7 @@ internal class ReworkedTerminalWidget(
}
override fun getComponent(): JComponent {
return panel
return view.component
}
override fun getPreferredFocusableComponent(): JComponent {
@@ -81,10 +75,7 @@ internal class ReworkedTerminalWidget(
}
override fun addNotification(notificationComponent: JComponent, disposable: Disposable) {
panel.addToTop(notificationComponent)
Disposer.register(disposable) {
panel.remove(notificationComponent)
}
view.setTopComponent(notificationComponent, disposable)
}
override fun writePlainMessage(message: @Nls String) {