Skip to content

Commit

Permalink
feat: support interactive command on all platforms
Browse files Browse the repository at this point in the history
  • Loading branch information
typfel committed Oct 2, 2024
1 parent 8b65be7 commit d5c2218
Show file tree
Hide file tree
Showing 11 changed files with 275 additions and 284 deletions.
2 changes: 2 additions & 0 deletions cli/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ tasks.jar {
kotlin {
applyDefaultHierarchyTemplate()
val jvmTarget = jvm {
withJava()
commonJvmConfig(includeNativeInterop = false)
tasks.named("run", JavaExec::class) {
isIgnoreExitValue = true
Expand Down Expand Up @@ -75,6 +76,7 @@ kotlin {
implementation(libs.coroutines.core)
implementation(libs.ktxDateTime)
implementation(libs.mordant)
implementation(libs.mordant.coroutines)
implementation(libs.ktxSerialization)
implementation(libs.ktxIO)
}
Expand Down
111 changes: 0 additions & 111 deletions cli/src/appleMain/kotlin/com/wire/kalium/cli/commands/ActionFlow.kt

This file was deleted.

168 changes: 84 additions & 84 deletions cli/src/appleMain/kotlin/com/wire/kalium/cli/commands/InputFlow.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,88 +17,88 @@
*/
package com.wire.kalium.cli.commands

import kotlinx.cinterop.ByteVar
import kotlinx.cinterop.addressOf
import kotlinx.cinterop.alloc
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.ptr
import kotlinx.cinterop.usePinned
import kotlinx.cinterop.value
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import platform.posix.STDIN_FILENO
import platform.posix.read
import platform.posix.ssize_t
// import kotlinx.cinterop.ByteVar
// import kotlinx.cinterop.addressOf
// import kotlinx.cinterop.alloc
// import kotlinx.cinterop.memScoped
// import kotlinx.cinterop.ptr
// import kotlinx.cinterop.usePinned
// import kotlinx.cinterop.value
// import kotlinx.coroutines.Dispatchers
// import kotlinx.coroutines.delay
// import kotlinx.coroutines.flow.Flow
// import kotlinx.coroutines.flow.flow
// import kotlinx.coroutines.flow.flowOn
// import platform.posix.STDIN_FILENO
// import platform.posix.read
// import platform.posix.ssize_t

@Suppress("MagicNumber")
fun inputFlow(): Flow<Input> = flow {
while (true) {
emit(readChar())
delay(100) // TODO jacob avoid this hack by enabling read timeout
}
}.flowOn(Dispatchers.Default)

@Suppress("ComplexMethod", "TooGenericExceptionThrown", "MagicNumber")
fun readChar(): Input =
memScoped {
val byte = alloc<ByteVar>()
var numBytesRead: ssize_t
while (read(STDIN_FILENO, byte.ptr, 1).also { numBytesRead = it } != 1L) {
if (numBytesRead == -1L) { throw RuntimeException("Failed to read input") }
}

val char = byte.value.toInt().toChar()
if (char == '\u001b') {
val sequence = ByteArray(3)
sequence.usePinned {
if (read(STDIN_FILENO, it.addressOf(0), 1) != 1L) { return Input.Character('\u001b') }
if (read(STDIN_FILENO, it.addressOf(1), 1) != 1L) { return Input.Character('\u001b') }
}

if (sequence[0].toInt().toChar() == '[') {
when (sequence[1].toInt().toChar()) {
in '0'..'9' -> {
sequence.usePinned {
if (read(STDIN_FILENO, it.addressOf(2), 1) != 1L) { return Input.Character('\u001b') }
}
if (sequence[2].toInt().toChar() == '~') {
when (sequence[1].toInt().toChar()) {
'1', '7' -> return Input.HomeKey
'3', '8' -> return Input.DeleteKey
'4' -> return Input.EndKey
'5' -> return Input.PageUp
'6' -> return Input.PageDown
}
}
}
'A' -> return Input.ArrowUp
'B' -> return Input.ArrowDown
'C' -> return Input.ArrowRight
'D' -> return Input.ArrowLeft
}
}
}

@Suppress("MagicNumber")
when (char.code) {
127 -> return Input.Backspace
else -> return Input.Character(char)
}
}

sealed class Input {
data class Character(val char: Char) : Input()
data object ArrowUp : Input()
data object ArrowDown : Input()
data object ArrowLeft : Input()
data object ArrowRight : Input()
data object HomeKey : Input()
data object EndKey : Input()
data object DeleteKey : Input()
data object PageUp : Input()
data object PageDown : Input()
data object Backspace : Input()
}
// @Suppress("MagicNumber")
// fun inputFlow(): Flow<Input> = flow {
// while (true) {
// emit(readChar())
// delay(100) // TODO jacob avoid this hack by enabling read timeout
// }
// }.flowOn(Dispatchers.Default)
//
// @Suppress("ComplexMethod", "TooGenericExceptionThrown", "MagicNumber")
// fun readChar(): Input =
// memScoped {
// val byte = alloc<ByteVar>()
// var numBytesRead: ssize_t
// while (read(STDIN_FILENO, byte.ptr, 1).also { numBytesRead = it } != 1L) {
// if (numBytesRead == -1L) { throw RuntimeException("Failed to read input") }
// }
//
// val char = byte.value.toInt().toChar()
// if (char == '\u001b') {
// val sequence = ByteArray(3)
// sequence.usePinned {
// if (read(STDIN_FILENO, it.addressOf(0), 1) != 1L) { return Input.Character('\u001b') }
// if (read(STDIN_FILENO, it.addressOf(1), 1) != 1L) { return Input.Character('\u001b') }
// }
//
// if (sequence[0].toInt().toChar() == '[') {
// when (sequence[1].toInt().toChar()) {
// in '0'..'9' -> {
// sequence.usePinned {
// if (read(STDIN_FILENO, it.addressOf(2), 1) != 1L) { return Input.Character('\u001b') }
// }
// if (sequence[2].toInt().toChar() == '~') {
// when (sequence[1].toInt().toChar()) {
// '1', '7' -> return Input.HomeKey
// '3', '8' -> return Input.DeleteKey
// '4' -> return Input.EndKey
// '5' -> return Input.PageUp
// '6' -> return Input.PageDown
// }
// }
// }
// 'A' -> return Input.ArrowUp
// 'B' -> return Input.ArrowDown
// 'C' -> return Input.ArrowRight
// 'D' -> return Input.ArrowLeft
// }
// }
// }
//
// @Suppress("MagicNumber")
// when (char.code) {
// 127 -> return Input.Backspace
// else -> return Input.Character(char)
// }
// }
//
// sealed class Input {
// data class Character(val char: Char) : Input()
// data object ArrowUp : Input()
// data object ArrowDown : Input()
// data object ArrowLeft : Input()
// data object ArrowRight : Input()
// data object HomeKey : Input()
// data object EndKey : Input()
// data object DeleteKey : Input()
// data object PageUp : Input()
// data object PageDown : Input()
// data object Backspace : Input()
// }
70 changes: 35 additions & 35 deletions cli/src/appleMain/kotlin/com/wire/kalium/cli/commands/RawMode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,39 +17,39 @@
*/
package com.wire.kalium.cli.commands

import com.github.ajalt.mordant.terminal.Terminal
import kotlinx.cinterop.alloc
import kotlinx.cinterop.convert
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.ptr
import platform.posix.ECHO
import platform.posix.ICANON
import platform.posix.STDOUT_FILENO
import platform.posix.TCSAFLUSH
import platform.posix.tcgetattr
import platform.posix.tcsetattr
import platform.posix.termios
// import com.github.ajalt.mordant.terminal.Terminal
// import kotlinx.cinterop.alloc
// import kotlinx.cinterop.convert
// import kotlinx.cinterop.memScoped
// import kotlinx.cinterop.ptr
// import platform.posix.ECHO
// import platform.posix.ICANON
// import platform.posix.STDOUT_FILENO
// import platform.posix.TCSAFLUSH
// import platform.posix.tcgetattr
// import platform.posix.tcsetattr
// import platform.posix.termios

fun Terminal.setRawMode(enabled: Boolean) = memScoped {
val termios = alloc<termios>()
if (tcgetattr(STDOUT_FILENO, termios.ptr) != 0) {
return@memScoped
}

if (enabled) {
termios.c_lflag = termios.c_lflag and ICANON.inv().convert()
termios.c_lflag = termios.c_lflag and ECHO.inv().convert()
} else {
termios.c_lflag = termios.c_lflag or ICANON.convert()
termios.c_lflag = termios.c_lflag or ECHO.convert()
}

tcsetattr(0, TCSAFLUSH, termios.ptr)
}

inline fun <T> Terminal.withRawMode(block: () -> T): T {
setRawMode(true)
val result = block()
setRawMode(false)
return result
}
// fun Terminal.setRawMode(enabled: Boolean) = memScoped {
// val termios = alloc<termios>()
// if (tcgetattr(STDOUT_FILENO, termios.ptr) != 0) {
// return@memScoped
// }
//
// if (enabled) {
// termios.c_lflag = termios.c_lflag and ICANON.inv().convert()
// termios.c_lflag = termios.c_lflag and ECHO.inv().convert()
// } else {
// termios.c_lflag = termios.c_lflag or ICANON.convert()
// termios.c_lflag = termios.c_lflag or ECHO.convert()
// }
//
// tcsetattr(0, TCSAFLUSH, termios.ptr)
// }
//
// inline fun <T> Terminal.withRawMode(block: () -> T): T {
// setRawMode(true)
// val result = block()
// setRawMode(false)
// return result
// }
Loading

0 comments on commit d5c2218

Please sign in to comment.