[terminal] IJPL-182482 Refactor: use TtyListener instead of additional tty connector wrapping

GitOrigin-RevId: 90e3eed07269cb40ee99c2b1f704f16b44b4043a
This commit is contained in:
Konstantin Hudyakov
2025-04-15 07:32:53 +00:00
committed by intellij-monorepo-bot
parent d27e7a6e4e
commit b4d528a3b0
4 changed files with 41 additions and 53 deletions
@@ -25,15 +25,20 @@ class ObservableTtyConnector(delegate: TtyConnector) : ProxyTtyConnector {
override fun read(buf: CharArray, offset: Int, length: Int): Int {
val charsReadCount = connector.read(buf, offset, length)
if (charsReadCount > 0) {
fireCharsRead(buf, offset, charsReadCount)
fireListeners { it.charsRead(buf, offset, length) }
}
return charsReadCount
}
private fun fireCharsRead(buf: CharArray, offset: Int, length: Int) {
override fun write(bytes: ByteArray) {
connector.write(bytes)
fireListeners { it.bytesWritten(bytes) }
}
private inline fun fireListeners(action: (TtyConnectorListener) -> Unit) {
for (listener in listeners) {
try {
listener.charsRead(buf, offset, length)
action(listener)
}
catch (t: Throwable) {
thisLogger().error(t)
@@ -41,10 +46,6 @@ class ObservableTtyConnector(delegate: TtyConnector) : ProxyTtyConnector {
}
}
override fun write(bytes: ByteArray?) {
connector.write(bytes)
}
override fun write(string: String?) {
connector.write(string)
}
@@ -7,9 +7,11 @@ import com.intellij.platform.util.coroutines.childScope
import com.intellij.terminal.JBTerminalSystemSettingsProviderBase
import com.intellij.terminal.TerminalExecutorServiceManagerImpl
import com.intellij.terminal.backend.fus.enableFus
import com.intellij.terminal.backend.fus.installFusListener
import com.intellij.terminal.session.TerminalSession
import com.intellij.terminal.session.TerminalSessionTerminatedEvent
import com.intellij.util.AwaitCancellationAndInvoke
import com.intellij.util.asDisposable
import com.intellij.util.awaitCancellationAndInvoke
import com.jediterm.core.typeahead.TerminalTypeAheadManager
import com.jediterm.core.util.TermSize
@@ -55,9 +57,11 @@ internal fun createTerminalSession(
coroutineScope: CoroutineScope,
fusActivity: BackendOutputActivity,
): TerminalSession {
val connector = enableFus(ttyConnector, fusActivity)
val observableTtyConnector = ttyConnector as? ObservableTtyConnector ?: ObservableTtyConnector(ttyConnector)
installFusListener(observableTtyConnector, fusActivity, parentDisposable = coroutineScope.asDisposable())
val maxHistoryLinesCount = AdvancedSettings.getInt("terminal.buffer.max.lines.count")
val services: JediTermServices = createJediTermServices(connector, fusActivity, initialSize, maxHistoryLinesCount, settings)
val services: JediTermServices = createJediTermServices(observableTtyConnector, fusActivity, initialSize, maxHistoryLinesCount, settings)
val outputScope = coroutineScope.childScope("Terminal output forwarding")
val shellIntegrationController = TerminalShellIntegrationController(services.controller)
@@ -4,5 +4,7 @@ import org.jetbrains.annotations.ApiStatus
@ApiStatus.Internal
interface TtyConnectorListener {
fun charsRead(buf: CharArray, offset: Int, length: Int)
fun charsRead(buf: CharArray, offset: Int, length: Int) {}
fun bytesWritten(bytes: ByteArray) {}
}
@@ -1,16 +1,15 @@
package com.intellij.terminal.backend.fus
import com.intellij.openapi.Disposable
import com.intellij.openapi.diagnostic.logger
import com.intellij.platform.rpc.UID
import com.intellij.terminal.backend.ObservableTtyConnector
import com.intellij.terminal.backend.TtyConnectorListener
import com.intellij.terminal.session.TerminalContentUpdatedEvent
import com.intellij.terminal.session.TerminalWriteBytesEvent
import com.jediterm.core.util.TermSize
import com.jediterm.terminal.TerminalDataStream
import com.jediterm.terminal.TtyBasedArrayDataStream
import com.jediterm.terminal.TtyConnector
import fleet.multiplatform.shims.ConcurrentHashMap
import org.jetbrains.plugins.terminal.fus.BackendLatencyService
import org.jetbrains.plugins.terminal.fus.BackendLatencyService.Companion.getInstance
import org.jetbrains.plugins.terminal.fus.BackendOutputActivity
import org.jetbrains.plugins.terminal.fus.BackendTypingActivity
import org.jetbrains.plugins.terminal.fus.ReworkedTerminalUsageCollector
@@ -37,8 +36,27 @@ internal class BackendLatencyServiceImpl : BackendLatencyService {
}
}
internal fun enableFus(ttyConnector: TtyConnector, fusActivity: BackendOutputActivity): TtyConnector =
FusAwareTtyConnector(ttyConnector, fusActivity)
internal fun installFusListener(
ttyConnector: ObservableTtyConnector,
fusActivity: BackendOutputActivity,
parentDisposable: Disposable,
) {
ttyConnector.addListener(parentDisposable, object : TtyConnectorListener {
override fun charsRead(buf: CharArray, offset: Int, length: Int) {
fusActivity.charsRead(length)
}
override fun bytesWritten(bytes: ByteArray) {
val typingActivity = BackendLatencyService.getInstance().getBackendTypingActivityOrNull(bytes) ?: return
try {
typingActivity.reportDuration()
}
finally {
typingActivity.finishBytesProcessing()
}
}
})
}
internal fun enableFus(stream: TerminalDataStream, fusActivity: BackendOutputActivity): TerminalDataStream =
FusAwareTtyBasedDataStream(stream, fusActivity)
@@ -296,43 +314,6 @@ private class BackendOutputActivityImpl : BackendOutputActivity {
}
}
private class FusAwareTtyConnector(private val original: TtyConnector, private val outputActivity: BackendOutputActivity) : TtyConnector {
override fun read(buf: CharArray, offset: Int, length: Int): Int = original.read(buf, offset, length).also { charsRead ->
outputActivity.charsRead(charsRead)
}
override fun write(bytes: ByteArray) {
val typingActivity = getInstance().getBackendTypingActivityOrNull(bytes)
try {
original.write(bytes)
typingActivity?.reportDuration()
}
finally {
typingActivity?.finishBytesProcessing()
}
}
override fun write(string: String) {
original.write(string)
}
override fun isConnected(): Boolean = original.isConnected
override fun waitFor(): Int = original.waitFor()
override fun ready(): Boolean = original.ready()
override fun getName(): String? = original.name
override fun close() {
original.close()
}
override fun resize(termSize: TermSize) {
original.resize(termSize)
}
}
// used to track individual instances of data classes
private class IdentityWrapper<T : Any>(private val instance: T) {
override fun equals(other: Any?): Boolean = instance === (other as? IdentityWrapper<T>)?.instance