diff --git a/plugins/terminal/backend/src/com/intellij/terminal/backend/BackendTerminalInput.kt b/plugins/terminal/backend/src/com/intellij/terminal/backend/BackendTerminalInput.kt index 38f190b89d43..eb0e3a77121d 100644 --- a/plugins/terminal/backend/src/com/intellij/terminal/backend/BackendTerminalInput.kt +++ b/plugins/terminal/backend/src/com/intellij/terminal/backend/BackendTerminalInput.kt @@ -12,6 +12,7 @@ import org.jetbrains.plugins.terminal.block.ui.withLock import org.jetbrains.plugins.terminal.util.STOP_EMULATOR_TIMEOUT import org.jetbrains.plugins.terminal.util.waitFor import java.util.concurrent.CancellationException +import kotlin.time.TimeSource internal fun createTerminalInputChannel( services: JediTermServices, @@ -50,7 +51,7 @@ private fun handleInputEvent(event: TerminalInputEvent, services: JediTermServic when (event) { is TerminalWriteBytesEvent -> { - terminalStarter.sendBytes(event.bytes, false) + terminalStarter.sendTrackedBytes(event.bytes, event.id, eventTime = TimeSource.Monotonic.markNow()) } is TerminalResizeEvent -> { terminalStarter.postResize(event.newSize.toTermSize(), RequestOrigin.User) diff --git a/plugins/terminal/backend/src/com/intellij/terminal/backend/StopAwareTerminalStarter.kt b/plugins/terminal/backend/src/com/intellij/terminal/backend/StopAwareTerminalStarter.kt deleted file mode 100644 index 8a69b2d56ed6..000000000000 --- a/plugins/terminal/backend/src/com/intellij/terminal/backend/StopAwareTerminalStarter.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.intellij.terminal.backend - -import com.jediterm.core.typeahead.TerminalTypeAheadManager -import com.jediterm.terminal.TerminalDataStream -import com.jediterm.terminal.TerminalExecutorServiceManager -import com.jediterm.terminal.TerminalStarter -import com.jediterm.terminal.TtyConnector -import com.jediterm.terminal.model.JediTerminal - -internal class StopAwareTerminalStarter( - terminal: JediTerminal, - ttyConnector: TtyConnector, - dataStream: TerminalDataStream, - typeAheadManager: TerminalTypeAheadManager, - executorServiceManager: TerminalExecutorServiceManager, -) : TerminalStarter(terminal, ttyConnector, dataStream, typeAheadManager, executorServiceManager) { - @Volatile - var isStopped: Boolean = false - private set - - override fun requestEmulatorStop() { - super.requestEmulatorStop() - isStopped = true - } -} diff --git a/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalOutput.kt b/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalOutput.kt index 950039b354a7..f19f0c21147c 100644 --- a/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalOutput.kt +++ b/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalOutput.kt @@ -230,7 +230,7 @@ internal fun createTerminalOutputFlow( return outputFlow } -private fun ensureEmulationActive(starter: StopAwareTerminalStarter) { +private fun ensureEmulationActive(starter: TerminalStarterEx) { if (Thread.interrupted() || starter.isStopped) { throw CancellationException("Terminal emulation was stopped") } diff --git a/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalSessionStart.kt b/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalSessionStart.kt index cdd4622c0698..a165ef00ccbc 100644 --- a/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalSessionStart.kt +++ b/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalSessionStart.kt @@ -104,7 +104,7 @@ private fun createJediTermServices( val controller = ObservableJediTerminal(terminalDisplay, textBuffer, styleState) val typeAheadManager = TerminalTypeAheadManager(JediTermTypeAheadModel(controller, textBuffer, settings)) val executorService = TerminalExecutorServiceManagerImpl() - val terminalStarter = StopAwareTerminalStarter( + val terminalStarter = TerminalStarterEx( controller, connector, TtyBasedArrayDataStream(connector), @@ -137,6 +137,6 @@ internal class JediTermServices( val terminalDisplay: TerminalDisplayImpl, val controller: ObservableJediTerminal, val executorService: TerminalExecutorServiceManager, - val terminalStarter: StopAwareTerminalStarter, + val terminalStarter: TerminalStarterEx, val ttyConnector: ObservableTtyConnector, ) diff --git a/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalStarterEx.kt b/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalStarterEx.kt new file mode 100644 index 000000000000..a0e1c8470939 --- /dev/null +++ b/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalStarterEx.kt @@ -0,0 +1,68 @@ +package com.intellij.terminal.backend + +import com.intellij.openapi.diagnostic.thisLogger +import com.jediterm.core.input.KeyEvent +import com.jediterm.core.typeahead.TerminalTypeAheadManager +import com.jediterm.terminal.TerminalDataStream +import com.jediterm.terminal.TerminalExecutorServiceManager +import com.jediterm.terminal.TerminalStarter +import com.jediterm.terminal.TtyConnector +import com.jediterm.terminal.model.JediTerminal +import org.jetbrains.plugins.terminal.fus.ReworkedTerminalUsageCollector +import java.io.IOException +import java.util.concurrent.ScheduledExecutorService +import kotlin.time.TimeMark + +internal class TerminalStarterEx( + terminal: JediTerminal, + private val ttyConnector: TtyConnector, + dataStream: TerminalDataStream, + typeAheadManager: TerminalTypeAheadManager, + executorServiceManager: TerminalExecutorServiceManager, +) : TerminalStarter(terminal, ttyConnector, dataStream, typeAheadManager, executorServiceManager) { + @Volatile + var isStopped: Boolean = false + private set + + private val singleThreadScheduledExecutor: ScheduledExecutorService = executorServiceManager.singleThreadScheduledExecutor + + @Volatile + private var isLastSentByteEscape = false + + override fun requestEmulatorStop() { + super.requestEmulatorStop() + isStopped = true + } + + /** + * Use for sending bytes for that typing latency should be reported. + * [eventTime] is the moment when this writing bytes event was initialized. + */ + fun sendTrackedBytes(bytes: ByteArray, eventId: Int, eventTime: TimeMark) { + val length = bytes.size + if (length > 0) { + isLastSentByteEscape = bytes[length - 1].toInt() == KeyEvent.VK_ESCAPE + } + execute { + try { + ttyConnector.write(bytes) + + val latency = eventTime.elapsedNow() + ReworkedTerminalUsageCollector.logBackendTypingLatency(eventId, latency) + } + catch (e: IOException) { + thisLogger().info("Cannot write to TtyConnector ${ttyConnector.javaClass.getName()}, connected: ${ttyConnector.isConnected}", e) + } + } + } + + override fun isLastSentByteEscape(): Boolean { + return isLastSentByteEscape || super.isLastSentByteEscape() + } + + private inline fun execute(crossinline action: () -> Unit) { + if (!singleThreadScheduledExecutor.isShutdown) { + singleThreadScheduledExecutor.execute { action() } + } + } +} diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/fus/ReworkedTerminalUsageCollector.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/fus/ReworkedTerminalUsageCollector.kt index 8435e925190a..2ce55b8dc6fc 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/fus/ReworkedTerminalUsageCollector.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/fus/ReworkedTerminalUsageCollector.kt @@ -99,7 +99,7 @@ object ReworkedTerminalUsageCollector : CounterUsagesCollector() { } @ApiStatus.Internal - fun logBackendLatency(inputEventId: Int, duration: Duration) { + fun logBackendTypingLatency(inputEventId: Int, duration: Duration) { backendTypingLatencyEvent.log( EVENT_ID_FIELD with inputEventId, DURATION_FIELD with duration