Skip to content

Commit

Permalink
Add run command and log panel (#40)
Browse files Browse the repository at this point in the history
  • Loading branch information
romainguy authored May 8, 2024
1 parent ff7af6c commit ee22e06
Show file tree
Hide file tree
Showing 5 changed files with 202 additions and 29 deletions.
88 changes: 77 additions & 11 deletions src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/Disassembly.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,75 @@ import java.nio.file.Path
import java.util.stream.Collectors
import kotlin.io.path.extension

private const val TotalSteps = 6
private const val TotalDisassemblySteps = 6
private const val TotalRunSteps = 2
private const val Done = 1f

private val byteCodeParser = ByteCodeParser()
private val dexDumpParser = DexDumpParser()
private val oatDumpParser = OatDumpParser()

suspend fun disassemble(
suspend fun buildAndRun(
toolPaths: ToolPaths,
source: String,
onLogs: (String) -> Unit,
onStatusUpdate: (String, Float) -> Unit
) = coroutineScope {
val ui = currentCoroutineContext()

launch(Dispatchers.IO) {
var step = 0f

launch(ui) { onStatusUpdate("Compiling Kotlin…", step++ / TotalRunSteps) }

val directory = toolPaths.tempDirectory
cleanupClasses(directory)

val path = directory.resolve("KotlinExplorer.kt")
Files.writeString(path, source)

val kotlinc = process(
*buildKotlincCommand(toolPaths, path),
directory = directory
)

if (kotlinc.exitCode != 0) {
launch(ui) {
onLogs(kotlinc.output.replace(path.parent.toString() + "/", ""))
onStatusUpdate("Ready", Done)
}
return@launch
}

launch(ui) { onStatusUpdate("Running…", step++ / TotalRunSteps) }

val java = process(
*buildJavaCommand(toolPaths),
directory = directory
)

if (java.exitCode != 0) {
launch(ui) {
onLogs(java.output)
onStatusUpdate("Ready", Done)
}
return@launch
}

launch(ui) {
onLogs(java.output)
onStatusUpdate("Ready", Done)
}
}
}

suspend fun buildAndDisassemble(
toolPaths: ToolPaths,
source: String,
onByteCode: (CodeContent) -> Unit,
onDex: (CodeContent) -> Unit,
onOat: (CodeContent) -> Unit,
onLogs: (String) -> Unit,
onStatusUpdate: (String, Float) -> Unit,
optimize: Boolean
) = coroutineScope {
Expand All @@ -51,7 +107,7 @@ suspend fun disassemble(
launch(Dispatchers.IO) {
var step = 0f

launch(ui) { onStatusUpdate("Compiling Kotlin…", step++ / TotalSteps) }
launch(ui) { onStatusUpdate("Compiling Kotlin…", step++ / TotalDisassemblySteps) }

val directory = toolPaths.tempDirectory
cleanupClasses(directory)
Expand All @@ -66,13 +122,13 @@ suspend fun disassemble(

if (kotlinc.exitCode != 0) {
launch(ui) {
onDex(Error(kotlinc.output.replace(path.parent.toString() + "/", "")))
onLogs(kotlinc.output.replace(path.parent.toString() + "/", ""))
onStatusUpdate("Ready", Done)
}
return@launch
}

launch(ui) { onStatusUpdate("Disassembling ByteCode…", step++ / TotalSteps) }
launch(ui) { onStatusUpdate("Disassembling ByteCode…", step++ / TotalDisassemblySteps) }

val javap = process(
*buildJavapCommand(directory),
Expand All @@ -83,14 +139,15 @@ suspend fun disassemble(

if (javap.exitCode != 0) {
launch(ui) {
onLogs(javap.output)
onStatusUpdate("Ready", Done)
}
return@launch
}

launch(ui) {
val status = if (optimize) "Optimizing with R8…" else "Compiling with D8…"
onStatusUpdate(status, step++ / TotalSteps)
onStatusUpdate(status, step++ / TotalDisassemblySteps)
}

writeR8Rules(directory)
Expand All @@ -102,13 +159,13 @@ suspend fun disassemble(

if (r8.exitCode != 0) {
launch(ui) {
onDex(Error(r8.output))
onLogs(r8.output)
onStatusUpdate("Ready", Done)
}
return@launch
}

launch(ui) { onStatusUpdate("Disassembling DEX…", step++ / TotalSteps) }
launch(ui) { onStatusUpdate("Disassembling DEX…", step++ / TotalDisassemblySteps) }

val dexdump = process(
toolPaths.dexdump.toString(),
Expand All @@ -119,15 +176,15 @@ suspend fun disassemble(

if (dexdump.exitCode != 0) {
launch(ui) {
onDex(Error(dexdump.output))
onLogs(dexdump.output)
onStatusUpdate("Ready", Done)
}
return@launch
}

launch(ui) {
onDex(dexDumpParser.parse(dexdump.output))
onStatusUpdate("AOT compilation…", step++ / TotalSteps)
onStatusUpdate("AOT compilation…", step++ / TotalDisassemblySteps)
}

val push = process(
Expand Down Expand Up @@ -163,7 +220,7 @@ suspend fun disassemble(
return@launch
}

launch(ui) { onStatusUpdate("Disassembling OAT…", step++ / TotalSteps) }
launch(ui) { onStatusUpdate("Disassembling OAT…", step++ / TotalDisassemblySteps) }

val oatdump = process(
toolPaths.adb.toString(),
Expand All @@ -184,6 +241,15 @@ suspend fun disassemble(
}
}

private fun buildJavaCommand(toolPaths: ToolPaths): Array<String> {
val command = mutableListOf(
"java",
"-classpath",
toolPaths.kotlinLibs.joinToString(":") { jar -> jar.toString() } + ":${toolPaths.platform}" + ":.",
"KotlinExplorerKt")
return command.toTypedArray()
}

private fun buildJavapCommand(directory: Path): Array<String> {
val command = mutableListOf("javap", "-p", "-l", "-c")
val classFiles = Files
Expand Down
73 changes: 65 additions & 8 deletions src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/KotlinExplorer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@
package dev.romainguy.kotlin.explorer

import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
Expand All @@ -34,13 +37,14 @@ import androidx.compose.ui.input.key.Key.Companion.G
import androidx.compose.ui.input.key.Key.Companion.L
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.text.font.FontFamily
import androidx.compose.ui.text.style.TextAlign.Companion.Center
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.*
import androidx.compose.ui.window.WindowPosition.Aligned
import dev.romainguy.kotlin.explorer.Shortcut.Ctrl
import dev.romainguy.kotlin.explorer.Shortcut.CtrlShift
import dev.romainguy.kotlin.explorer.Shortcut.*
import dev.romainguy.kotlin.explorer.code.CodeContent
import dev.romainguy.kotlin.explorer.code.CodeStyle
import dev.romainguy.kotlin.explorer.code.CodeTextArea
Expand Down Expand Up @@ -82,6 +86,7 @@ private fun FrameWindowScope.KotlinExplorer(
var activeTextArea by remember { mutableStateOf<RSyntaxTextArea?>(null) }
var status by remember { mutableStateOf("Ready") }
var progress by remember { mutableStateOf(1f) }
var logs by remember { mutableStateOf("") }

val searchListener = remember { object : SearchListener {
override fun searchEvent(e: SearchEvent?) {
Expand Down Expand Up @@ -143,18 +148,24 @@ private fun FrameWindowScope.KotlinExplorer(
area.codeStyle = area.codeStyle.withShowLineNumbers(it)
}
}

val onProgressUpdate: (String, Float) -> Unit = { newStatus: String, newProgress: Float ->
status = newStatus
progress = newProgress
}
val onLogsUpdate: (String) -> Unit = { text ->
logs = text
if (text.isNotEmpty()) {
explorerState.showLogs = true
}
}

MainMenu(
explorerState,
sourceTextArea,
byteCodeTextArea::setContent,
dexTextArea::setContent,
oatTextArea::setContent,
onLogsUpdate,
onProgressUpdate,
{ findDialog.isVisible = true },
{ SearchEngine.find(activeTextArea, findDialog.searchContext) },
Expand All @@ -174,15 +185,39 @@ private fun FrameWindowScope.KotlinExplorer(
}

Column(modifier = Modifier.background(JewelTheme.globalColors.paneBackground)) {
MultiSplitter(modifier = Modifier.weight(1.0f), panels)
VerticalOptionalPanel(
modifier = Modifier.weight(1.0f),
showOptionalPanel = explorerState.showLogs,
optionalPanel = { LogsPanel(logs) }
) {
MultiSplitter(modifier = Modifier.weight(1.0f), panels = panels)
}
StatusBar(status, progress)
}
}

@Composable
private fun LogsPanel(logs: String) {
Column {
Title("Logs")
Text(
text = logs,
fontFamily = FontFamily.Monospace,
modifier = Modifier
.weight(1.0f)
.fillMaxSize()
.background(Color.White)
.verticalScroll(rememberScrollState())
.border(1.dp, JewelTheme.globalColors.borders.normal)
.padding(8.dp)
)
}
}

@Composable
private fun StatusBar(status: String, progress: Float) {
Row(verticalAlignment = CenterVertically) {
val width = 160.dp
val width = 190.dp
Text(
modifier = Modifier
.widthIn(min = width, max = width)
Expand Down Expand Up @@ -299,6 +334,7 @@ private fun FrameWindowScope.MainMenu(
onByteCodeUpdate: (CodeContent) -> Unit,
onDexUpdate: (CodeContent) -> Unit,
onOatUpdate: (CodeContent) -> Unit,
onLogsUpdate: (String) -> Unit,
onStatusUpdate: (String, Float) -> Unit,
onFindClicked: () -> Unit,
onFindNextClicked: () -> Unit,
Expand All @@ -311,17 +347,29 @@ private fun FrameWindowScope.MainMenu(

val compileAndDisassemble: () -> Unit = {
scope.launch {
disassemble(
buildAndDisassemble(
explorerState.toolPaths,
sourceTextArea.text,
onByteCodeUpdate,
onDexUpdate,
onOatUpdate,
onLogsUpdate,
onStatusUpdate,
explorerState.optimize
)
}
}
val compileAndRun: () -> Unit = {
scope.launch {
buildAndRun(
explorerState.toolPaths,
sourceTextArea.text,
onLogsUpdate,
onStatusUpdate
)
}
}

MenuBar {
Menu("File") {
Item("Settings…", onClick = onOpenSettings)
Expand All @@ -339,14 +387,23 @@ private fun FrameWindowScope.MainMenu(
onShowLineNumberChanged(it)
}
Separator()
MenuCheckboxItem("Show Logs", Ctrl(L), explorerState::showLogs)
Separator()
MenuCheckboxItem("Presentation Mode", CtrlShift(P), explorerState::presentationMode) {
onPresentationModeChanged(it)
}
}
Menu("Compilation") {
Menu("Build") {
MenuItem(
"Run",
CtrlOnly(R),
onClick = compileAndRun,
enabled = explorerState.toolPaths.isValid
)
Separator()
MenuCheckboxItem("Optimize with R8", CtrlShift(O), explorerState::optimize)
MenuItem(
"Compile & Disassemble",
"Build & Disassemble",
CtrlShift(D),
onClick = compileAndDisassemble,
enabled = explorerState.toolPaths.isValid
Expand Down
16 changes: 14 additions & 2 deletions src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/Menu.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,23 @@ import androidx.compose.ui.window.MenuScope
import kotlin.reflect.KMutableProperty0

/** Convenience class handles `Ctrl <-> Meta` modifies */
sealed class Shortcut(private val key: Key, private val isShift: Boolean, private val isCtrl: Boolean) {
sealed class Shortcut(
private val key: Key,
private val isShift: Boolean,
private val isCtrl: Boolean,
private val metaOnMac: Boolean = true
) {
class Ctrl(key: Key) : Shortcut(key, isCtrl = true, isShift = false)
class CtrlOnly(key: Key) : Shortcut(key, isCtrl = true, isShift = false, metaOnMac = false)
class CtrlShift(key: Key) : Shortcut(key, isCtrl = true, isShift = true)

fun asKeyShortcut() = KeyShortcut(key = key, ctrl = isCtrl && !isMac, shift = isShift, meta = isCtrl && isMac)
fun asKeyShortcut() =
KeyShortcut(
key = key,
ctrl = isCtrl && (!isMac || !metaOnMac),
shift = isShift,
meta = isCtrl && isMac && metaOnMac
)
}

@Composable
Expand Down
Loading

0 comments on commit ee22e06

Please sign in to comment.