mirror of
https://github.com/JakeWharton/mosaic.git
synced 2025-10-30 02:06:19 +08:00
Always enable in-band resize mode even if already set (#871)
This should cause the terminal to emit the current size.
This commit is contained in:
@ -4,8 +4,9 @@
|
||||
[Unreleased]: https://github.com/JakeWharton/mosaic/compare/0.16.0...HEAD
|
||||
|
||||
Changed:
|
||||
- Unsolicited focus and theme events are now ignored unless the terminal has reported that it supports focus and theme, respectively.
|
||||
This should have no real impact on anything, except that now the `Terminal.capabilities` value can now be trusted to indicate whether `Terminal.state` will ever change.
|
||||
- Unsolicited focus, theme, and resize events are now ignored unless the terminal has reported that it supports each of those modes.
|
||||
This should have no real impact on anything, except that now the `Terminal.capabilities` value can now be trusted to indicate whether `Terminal.state` will ever change in the case of focus and theme.
|
||||
For terminal size, a platform-specific fallback exists which will attempt to still correctly report the size, but asynchronously.
|
||||
- Terminal theme is now always queried for an initial value regardless of whether the terminal supports sending theme changes.
|
||||
- Java 11 is now the minimum-supported JVM version.
|
||||
|
||||
|
||||
@ -214,10 +214,10 @@ public suspend fun Tty.asTerminalIn(
|
||||
}
|
||||
|
||||
inBandResizeMode -> {
|
||||
inBandResizeEvents = event.setting.isSupported
|
||||
if (event.setting == Setting.Reset) {
|
||||
toggleInBandResize = true
|
||||
// Enabling in-band resize will trigger an initial event.
|
||||
if (event.setting.isSupported) {
|
||||
inBandResizeEvents = true
|
||||
toggleInBandResize = event.setting == Setting.Reset
|
||||
// Enabling in-band resize (even if already set) should trigger an initial event.
|
||||
write(inBandResizeEnable)
|
||||
}
|
||||
}
|
||||
@ -226,7 +226,7 @@ public suspend fun Tty.asTerminalIn(
|
||||
|
||||
is OperatingStatusResponseEvent -> {
|
||||
if (stage == StageCapabilityQueries) {
|
||||
if (focusEvents or toggleInBandResize) {
|
||||
if (focusEvents or inBandResizeEvents) {
|
||||
// By enabling these modes (or by sending an explicit default value query after
|
||||
// enabling the mode) wait for a reply about the default with a second DSR.
|
||||
stage = StageDefaultQueries
|
||||
@ -297,7 +297,11 @@ public suspend fun Tty.asTerminalIn(
|
||||
}
|
||||
|
||||
is ResizeEvent -> {
|
||||
if (inBandResizeEvents) {
|
||||
size.value = Terminal.Size(event.columns, event.rows, event.width, event.height)
|
||||
} else {
|
||||
// TODO Report unsolicited resize events... somewhere.
|
||||
}
|
||||
}
|
||||
|
||||
is SystemThemeEvent -> {
|
||||
@ -332,7 +336,7 @@ public suspend fun Tty.asTerminalIn(
|
||||
write("\r\n")
|
||||
}
|
||||
|
||||
if (!toggleInBandResize) {
|
||||
if (!inBandResizeEvents) {
|
||||
currentSize().let { (columns, rows) ->
|
||||
size.value = Terminal.Size(columns, rows)
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ fun terminalTest(block: suspend TerminalTester.() -> Unit) {
|
||||
}
|
||||
|
||||
class TerminalTester(
|
||||
private val testTty: TestTty,
|
||||
val testTty: TestTty,
|
||||
) {
|
||||
private data class Expect(val output: ByteString, val reply: ByteString)
|
||||
private val expects = Channel<Expect>(UNLIMITED)
|
||||
|
||||
@ -0,0 +1,174 @@
|
||||
package com.jakewharton.mosaic.tty.terminal
|
||||
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.isEqualTo
|
||||
import assertk.assertions.isFalse
|
||||
import assertk.assertions.isTrue
|
||||
import com.jakewharton.mosaic.terminal.ResizeEvent
|
||||
import com.jakewharton.mosaic.terminal.Terminal
|
||||
import kotlin.test.Test
|
||||
|
||||
class TtyTerminalResizeTest {
|
||||
@Test fun noReply() = terminalTest {
|
||||
expect("${CSI}0c", reply = "$CSI?62;22c")
|
||||
expect("${CSI}5n", reply = "${CSI}0n")
|
||||
|
||||
val teardown = withTerminal { setup ->
|
||||
assertThat(capabilities.inBandResizeEvents).isFalse()
|
||||
assertThat(state.size.value).isEqualTo(Terminal.Size(80, 24, 0, 0))
|
||||
|
||||
// No attempt to enable in-band resize events.
|
||||
assertThat(setup).doesNotContain("$CSI?2048h")
|
||||
|
||||
// Write an unsolicited resize event which should be ignored.
|
||||
ptyWrite("${CSI}48;40;100;400;800t")
|
||||
assertThat(events.receive()).isEqualTo(ResizeEvent(100, 40, 800, 400))
|
||||
assertThat(state.size.value).isEqualTo(Terminal.Size(80, 24, 0, 0))
|
||||
|
||||
// Platform-specific resize event should be honored.
|
||||
testTty.resize(100, 40, 0, 0)
|
||||
assertThat(events.receive()).isEqualTo(ResizeEvent(100, 40, 0, 0))
|
||||
assertThat(state.size.value).isEqualTo(Terminal.Size(100, 40, 0, 0))
|
||||
}
|
||||
|
||||
// No attempt to disable in-band resize events.
|
||||
assertThat(teardown).doesNotContain("$CSI?2048l")
|
||||
}
|
||||
|
||||
@Test fun replySetNoInitialValue() = terminalTest {
|
||||
expect("${CSI}0c", reply = "$CSI?62;22c")
|
||||
// Resize events are set (i.e, enabled).
|
||||
expect("$CSI?2048\$p", reply = "$CSI?2048;1\$y")
|
||||
expect("${CSI}5n", reply = "${CSI}0n")
|
||||
// Second DSR is used to check for initial size.
|
||||
expect("${CSI}5n", reply = "${CSI}0n")
|
||||
|
||||
val teardown = withTerminal { setup ->
|
||||
assertThat(capabilities.inBandResizeEvents).isTrue()
|
||||
assertThat(state.size.value).isEqualTo(Terminal.Size(80, 24, 0, 0))
|
||||
|
||||
// Resize events re-enabled to trigger initial value reply.
|
||||
assertThat(setup).contains("$CSI?2048h")
|
||||
|
||||
// Write resize events which should be honored.
|
||||
ptyWrite("${CSI}48;40;100;400;800t")
|
||||
assertThat(events.receive()).isEqualTo(ResizeEvent(100, 40, 800, 400))
|
||||
assertThat(state.size.value).isEqualTo(Terminal.Size(100, 40, 800, 400))
|
||||
}
|
||||
|
||||
// Resize events are left enabled.
|
||||
assertThat(teardown).doesNotContain("$CSI?2048l")
|
||||
}
|
||||
|
||||
@Test fun replySetWithInitialValue() = terminalTest {
|
||||
expect("${CSI}0c", reply = "$CSI?62;22c")
|
||||
// Resize events are set (i.e, enabled).
|
||||
expect("$CSI?2048\$p", reply = "$CSI?2048;1\$y")
|
||||
expect("${CSI}5n", reply = "${CSI}0n")
|
||||
expect("$CSI?2048h", reply = "${CSI}48;30;90;300;700t")
|
||||
expect("${CSI}5n", reply = "${CSI}0n")
|
||||
|
||||
withTerminal {
|
||||
assertThat(state.size.value).isEqualTo(Terminal.Size(90, 30, 700, 300))
|
||||
}
|
||||
}
|
||||
|
||||
@Test fun replyResetNoInitialValue() = terminalTest {
|
||||
expect("${CSI}0c", reply = "$CSI?62;22c")
|
||||
// Resize events are reset (i.e, not enabled).
|
||||
expect("$CSI?2048\$p", reply = "$CSI?2048;2\$y")
|
||||
expect("${CSI}5n", reply = "${CSI}0n")
|
||||
// Second DSR is used to check for initial size.
|
||||
expect("${CSI}5n", reply = "${CSI}0n")
|
||||
|
||||
val teardown = withTerminal { setup ->
|
||||
assertThat(capabilities.inBandResizeEvents).isTrue()
|
||||
assertThat(state.size.value).isEqualTo(Terminal.Size(80, 24, 0, 0))
|
||||
|
||||
// Enable resize events.
|
||||
assertThat(setup).contains("$CSI?2048h")
|
||||
|
||||
// Write resize events which should be honored.
|
||||
ptyWrite("${CSI}48;40;100;400;800t")
|
||||
assertThat(events.receive()).isEqualTo(ResizeEvent(100, 40, 800, 400))
|
||||
assertThat(state.size.value).isEqualTo(Terminal.Size(100, 40, 800, 400))
|
||||
}
|
||||
|
||||
// Disable resize events.
|
||||
assertThat(teardown).contains("$CSI?2048l")
|
||||
}
|
||||
|
||||
@Test fun replyResetWithInitialValue() = terminalTest {
|
||||
expect("${CSI}0c", reply = "$CSI?62;22c")
|
||||
// Resize events are reset (i.e, not enabled).
|
||||
expect("$CSI?2048\$p", reply = "$CSI?2048;2\$y")
|
||||
expect("${CSI}5n", reply = "${CSI}0n")
|
||||
expect("$CSI?2048h", reply = "${CSI}48;30;90;300;700t")
|
||||
expect("${CSI}5n", reply = "${CSI}0n")
|
||||
|
||||
withTerminal {
|
||||
assertThat(state.size.value).isEqualTo(Terminal.Size(90, 30, 700, 300))
|
||||
}
|
||||
}
|
||||
|
||||
@Test fun replyPermanentlySetNoInitialValue() = terminalTest {
|
||||
expect("${CSI}0c", reply = "$CSI?62;22c")
|
||||
// Resize events are permanently set (i.e, always enabled).
|
||||
expect("$CSI?2048\$p", reply = "$CSI?2048;3\$y")
|
||||
expect("${CSI}5n", reply = "${CSI}0n")
|
||||
// Second DSR is used to check for initial size.
|
||||
expect("${CSI}5n", reply = "${CSI}0n")
|
||||
|
||||
val teardown = withTerminal { setup ->
|
||||
assertThat(capabilities.inBandResizeEvents).isTrue()
|
||||
assertThat(state.size.value).isEqualTo(Terminal.Size(80, 24, 0, 0))
|
||||
|
||||
// Focus events re-enabled to possibly trigger initial value reply.
|
||||
assertThat(setup).contains("$CSI?2048h")
|
||||
|
||||
// Write resize events which should be honored.
|
||||
ptyWrite("${CSI}48;40;100;400;800t")
|
||||
assertThat(events.receive()).isEqualTo(ResizeEvent(100, 40, 800, 400))
|
||||
assertThat(state.size.value).isEqualTo(Terminal.Size(100, 40, 800, 400))
|
||||
}
|
||||
|
||||
// No attempt to disable focus events.
|
||||
assertThat(teardown).doesNotContain("$CSI?2048l")
|
||||
}
|
||||
|
||||
@Test fun replyPermanentlySetWithInitialValue() = terminalTest {
|
||||
expect("${CSI}0c", reply = "$CSI?62;22c")
|
||||
// Resize events are permanently set (i.e, always enabled).
|
||||
expect("$CSI?2048\$p", reply = "$CSI?2048;3\$y")
|
||||
expect("${CSI}5n", reply = "${CSI}0n")
|
||||
expect("$CSI?2048h", reply = "${CSI}48;30;90;300;700t")
|
||||
expect("${CSI}5n", reply = "${CSI}0n")
|
||||
|
||||
withTerminal {
|
||||
assertThat(state.size.value).isEqualTo(Terminal.Size(90, 30, 700, 300))
|
||||
}
|
||||
}
|
||||
|
||||
@Test fun replyPermanentlyReset() = terminalTest {
|
||||
expect("${CSI}0c", reply = "$CSI?62;22c")
|
||||
// Resize events are permanently reset (i.e, not supported).
|
||||
expect("$CSI?2048\$p", reply = "$CSI?2048;4\$y")
|
||||
expect("${CSI}5n", reply = "${CSI}0n")
|
||||
|
||||
val teardown = withTerminal { setup ->
|
||||
assertThat(capabilities.inBandResizeEvents).isFalse()
|
||||
assertThat(state.size.value).isEqualTo(Terminal.Size(80, 24, 0, 0))
|
||||
|
||||
// No attempt to enable focus events.
|
||||
assertThat(setup).doesNotContain("$CSI?2048h")
|
||||
|
||||
// Write an unsolicited resize event which should be ignored.
|
||||
ptyWrite("${CSI}48;40;100;400;800t")
|
||||
assertThat(events.receive()).isEqualTo(ResizeEvent(100, 40, 800, 400))
|
||||
assertThat(state.size.value).isEqualTo(Terminal.Size(80, 24, 0, 0))
|
||||
}
|
||||
|
||||
// No attempt to disable focus events.
|
||||
assertThat(teardown).doesNotContain("$CSI?2048l")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user