Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support interactive command on all platforms #3049

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading