diff --git a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/Disassembly.kt b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/Disassembly.kt index e9dae9b8..3c4b47ab 100644 --- a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/Disassembly.kt +++ b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/Disassembly.kt @@ -16,6 +16,10 @@ package dev.romainguy.kotlin.explorer +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.withStyle import dev.romainguy.kotlin.explorer.build.ByteCodeDecompiler import dev.romainguy.kotlin.explorer.build.DexCompiler import dev.romainguy.kotlin.explorer.build.KotlinCompiler @@ -39,7 +43,7 @@ private val oatDumpParser = OatDumpParser() suspend fun buildAndRun( toolPaths: ToolPaths, source: String, - onLogs: (String) -> Unit, + onLogs: (AnnotatedString) -> Unit, onStatusUpdate: (String, Float) -> Unit ) = coroutineScope { val ui = currentCoroutineContext() @@ -59,7 +63,7 @@ suspend fun buildAndRun( if (kotlinc.exitCode != 0) { withContext(ui) { - onLogs(kotlinc.output.replace(path.parent.toString() + "/", "")) + onLogs(showError(kotlinc.output.replace(path.parent.toString() + "/", ""))) updater.advance("Error compiling Kotlin", 2) } return@launch @@ -73,10 +77,12 @@ suspend fun buildAndRun( ) withContext(ui) { - onLogs(java.output) + onLogs(showLogs(java.output)) val status = if (java.exitCode != 0) "Error running code" else "Run completed" updater.advance(status) } + + updater.addJob(launch(ui) { updater.update("Ready") }) } finally { withContext(ui) { updater.finish() } } @@ -89,7 +95,7 @@ suspend fun buildAndDisassemble( onByteCode: (CodeContent) -> Unit, onDex: (CodeContent) -> Unit, onOat: (CodeContent) -> Unit, - onLogs: (String) -> Unit, + onLogs: (AnnotatedString) -> Unit, onStatusUpdate: (String, Float) -> Unit, optimize: Boolean ) = coroutineScope { @@ -110,7 +116,7 @@ suspend fun buildAndDisassemble( if (kotlinc.exitCode != 0) { updater.addJob(launch(ui) { - onLogs(kotlinc.output.replace(path.parent.toString() + "/", "")) + onLogs(showError(kotlinc.output.replace(path.parent.toString() + "/", ""))) updater.advance("Error compiling Kotlin", updater.steps) }) return@launch @@ -121,7 +127,7 @@ suspend fun buildAndDisassemble( val javap = byteCodeDecompiler.decompile(directory) withContext(ui) { val status = if (javap.exitCode != 0) { - onLogs(javap.output) + onLogs(showError(javap.output)) "Error Disassembling Java ByteCode" } else { onByteCode(byteCodeParser.parse(javap.output)) @@ -137,7 +143,7 @@ suspend fun buildAndDisassemble( if (dex.exitCode != 0) { updater.addJob(launch(ui) { - onLogs(dex.output) + onLogs(showError(dex.output)) updater.advance("Error creating DEX", 5) }) return@launch @@ -150,7 +156,7 @@ suspend fun buildAndDisassemble( val dexdump = dexCompiler.dumpDex() withContext(ui) { val status = if (dexdump.exitCode != 0) { - onLogs(dexdump.output) + onLogs(showError(dexdump.output)) "Error creating DEX dump" } else { onDex(dexDumpParser.parse(dexdump.output)) @@ -170,7 +176,7 @@ suspend fun buildAndDisassemble( if (push.exitCode != 0) { updater.addJob(launch(ui) { - onLogs(push.output) + onLogs(showError(push.output)) updater.advance("Error pushing code to device", 3) }) return@launch @@ -189,8 +195,8 @@ suspend fun buildAndDisassemble( if (dex2oat.exitCode != 0) { updater.addJob(launch(ui) { - onLogs(dex2oat.output) - updater.advance("Ready", 2) + onLogs(showError(dex2oat.output)) + updater.advance("Error compiling OAT", 2) }) return@launch } @@ -208,12 +214,14 @@ suspend fun buildAndDisassemble( updater.addJob(launch(ui) { onOat(oatDumpParser.parse(oatdump.output)) }) val status = if (oatdump.exitCode != 0) { - onLogs(oatdump.output) + onLogs(showError(oatdump.output)) "Error creating oat dump" } else { "Created oat dump" } withContext(ui) { updater.advance(status) } + + updater.addJob(launch(ui) { updater.update("Ready") }) } finally { withContext(ui) { updater.finish() } } @@ -236,4 +244,12 @@ private fun cleanupClasses(directory: Path) { .forEach { path -> path.toFile().delete() } } +private fun showError(error: String) = buildAnnotatedString { + withStyle(SpanStyle(ErrorColor)) { + append(error) + } +} + +private fun showLogs(logs: String) = AnnotatedString(logs) + internal val BuiltInKotlinClass = Regex("^(kotlin|kotlinx|java|javax|org\\.(intellij|jetbrains))\\..+") diff --git a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/KotlinExplorer.kt b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/KotlinExplorer.kt index e636365e..e1bb5cdf 100644 --- a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/KotlinExplorer.kt +++ b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/KotlinExplorer.kt @@ -38,6 +38,7 @@ import androidx.compose.ui.input.key.Key.Companion.O import androidx.compose.ui.input.key.Key.Companion.P import androidx.compose.ui.input.key.Key.Companion.R import androidx.compose.ui.input.key.Key.Companion.S +import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.style.TextAlign.Companion.Center import androidx.compose.ui.unit.DpSize @@ -84,7 +85,7 @@ private class UiState(val explorerState: ExplorerState, window: ComposeWindow) { var previousActiveTextArea by mutableStateOf(null) var status by mutableStateOf("Ready") var progress by mutableStateOf(1f) - var logs by mutableStateOf("") + var logs by mutableStateOf(AnnotatedString("")) val searchListener = object : SearchListener { override fun searchEvent(e: SearchEvent?) { @@ -162,7 +163,7 @@ private class UiState(val explorerState: ExplorerState, window: ComposeWindow) { progress = newProgress } - val onLogsUpdate: (String) -> Unit = { text -> + val onLogsUpdate: (AnnotatedString) -> Unit = { text -> logs = text if (text.isNotEmpty()) { explorerState.showLogs = true @@ -227,7 +228,7 @@ private fun FrameWindowScope.KotlinExplorer( } @Composable -private fun LogsPanel(logs: String) { +private fun LogsPanel(logs: AnnotatedString) { Column { Title("Logs") SelectionContainer { @@ -259,8 +260,8 @@ private fun StatusBar(status: String, progress: Float) { if (progress < 1) { LinearProgressIndicator( progress = { progress }, - color = Color(0xff3369d6), - trackColor = Color(0xffc4c4c4), + color = ProgressColor, + trackColor = ProgressTrackColor, strokeCap = StrokeCap.Round ) } @@ -368,7 +369,7 @@ private fun FrameWindowScope.MainMenu( onByteCodeUpdate: (CodeContent) -> Unit, onDexUpdate: (CodeContent) -> Unit, onOatUpdate: (CodeContent) -> Unit, - onLogsUpdate: (String) -> Unit, + onLogsUpdate: (AnnotatedString) -> Unit, onStatusUpdate: (String, Float) -> Unit, onFindClicked: () -> Unit, onFindNextClicked: () -> Unit, @@ -492,7 +493,16 @@ fun main() = application { val explorerState = remember { ExplorerState() } - Runtime.getRuntime().addShutdownHook(Thread { explorerState.writeState() }) + val windowState = rememberWindowState( + size = explorerState.getWindowSize(), + position = explorerState.getWindowPosition(), + placement = explorerState.windowPlacement, + ) + + Runtime.getRuntime().addShutdownHook(Thread { + explorerState.setWindowState(windowState) + explorerState.writeState() + }) val themeDefinition = if (KotlinExplorerTheme.System.isDark()) { JewelTheme.darkThemeDefinition() @@ -510,11 +520,6 @@ fun main() = application { ComponentStyling.decoratedWindow(titleBarStyle = titleBarStyle), false ) { - val windowState = rememberWindowState( - size = explorerState.getWindowSize(), - position = explorerState.getWindowPosition(), - placement = explorerState.windowPlacement, - ) DecoratedWindow( state = windowState, onCloseRequest = { diff --git a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/ProgressUpdater.kt b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/ProgressUpdater.kt index 3bd4cc3d..ae6e8ea8 100644 --- a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/ProgressUpdater.kt +++ b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/ProgressUpdater.kt @@ -52,10 +52,9 @@ class ProgressUpdater( if (step < steps) { Logger.warn("finish() called but progress is not yet finished: step=$step") } - sendUpdate("Ready", steps) } - /** Add a job that needs to be joined before finishing*/ + /** Add a job that needs to be joined before finishing */ fun addJob(job: Job) { jobs.add(job) } diff --git a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/Theme.kt b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/Theme.kt index 799a525c..6b97aa31 100644 --- a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/Theme.kt +++ b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/Theme.kt @@ -16,8 +16,13 @@ package dev.romainguy.kotlin.explorer +import androidx.compose.ui.graphics.Color import org.jetbrains.skiko.SystemTheme +val ErrorColor = Color(0xffa04646) +val ProgressColor = Color(0xff3369d6) +val ProgressTrackColor = Color(0xffc4c4c4) + enum class KotlinExplorerTheme { Dark, Light, System;