Skip to content

Commit

Permalink
Add Indent & Line Number Width to Settings (#38)
Browse files Browse the repository at this point in the history
The indent only works for the decompiled code windows, not for the source.

It would be nice to be able to do that but RSyntaxTextArea doesn't really support reformating based on indent level.
  • Loading branch information
alonalbert authored May 7, 2024
1 parent e7440fe commit 4ba91f3
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 59 deletions.
28 changes: 18 additions & 10 deletions src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/KotlinExplorer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,8 @@ 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.code.CodeBuilder.LineNumberMode.FixedWidth
import dev.romainguy.kotlin.explorer.code.CodeBuilder.LineNumberMode.None
import dev.romainguy.kotlin.explorer.code.CodeContent
import dev.romainguy.kotlin.explorer.code.CodeStyle
import dev.romainguy.kotlin.explorer.code.CodeTextArea
import kotlinx.coroutines.launch
import org.fife.rsta.ui.search.FindDialog
Expand Down Expand Up @@ -74,8 +73,6 @@ import javax.swing.SwingUtilities

private const val FontSizeEditingMode = 12.0f
private const val FontSizePresentationMode = 20.0f
private const val LineNumberWidth = 4
private const val CodeIndent = 4

@Composable
private fun FrameWindowScope.KotlinExplorer(
Expand Down Expand Up @@ -127,6 +124,7 @@ private fun FrameWindowScope.KotlinExplorer(
val byteCodeTextArea = remember { byteCodeTextArea(explorerState, focusTracker) }
val dexTextArea = remember { dexTextArea(explorerState, focusTracker) }
val oatTextArea = remember { oatTextArea(explorerState, focusTracker) }
val codeTextAreas = listOf(byteCodeTextArea, dexTextArea, oatTextArea)

val findDialog = remember { FindDialog(window, searchListener).apply { searchContext.searchWrap = true } }
var showSettings by remember { mutableStateOf(!explorerState.toolPaths.isValid) }
Expand All @@ -141,7 +139,9 @@ private fun FrameWindowScope.KotlinExplorer(
listOf(dexTextArea, oatTextArea).forEach { area -> area.presentationMode = it }
}
val updateShowLineNumbers: (Boolean) -> Unit = {
listOf(byteCodeTextArea, dexTextArea).forEach { area -> area.lineNumberMode = it.toLineNumberMode() }
listOf(byteCodeTextArea, dexTextArea).forEach { area ->
area.codeStyle = area.codeStyle.withShowLineNumbers(it)
}
}

val onProgressUpdate: (String, Float) -> Unit = { newStatus: String, newProgress: Float ->
Expand All @@ -165,7 +165,12 @@ private fun FrameWindowScope.KotlinExplorer(
)

if (showSettings) {
Settings(explorerState, onDismissRequest = { showSettings = false })
Settings(explorerState, onDismissRequest = {
showSettings = false
codeTextAreas.forEach {
it.codeStyle = it.codeStyle.withSettings(explorerState.indent, explorerState.lineNumberWidth)
}
})
}

Column(modifier = Modifier.background(JewelTheme.globalColors.paneBackground)) {
Expand Down Expand Up @@ -281,8 +286,8 @@ private fun codeTextArea(
focusTracker: FocusListener,
hasLineNumbers: Boolean = true
): CodeTextArea {
val linNumberMode = (hasLineNumbers && state.showLineNumbers).toLineNumberMode()
return CodeTextArea(state.presentationMode, CodeIndent, linNumberMode).apply {
val codeStyle = CodeStyle(state.indent, state.showLineNumbers && hasLineNumbers, state.lineNumberWidth)
return CodeTextArea(state.presentationMode, codeStyle).apply {
configureSyntaxTextArea(SyntaxConstants.SYNTAX_STYLE_NONE, focusTracker)
}
}
Expand Down Expand Up @@ -377,8 +382,6 @@ private fun RSyntaxTextArea.updateStyle(explorerState: ExplorerState) {
font = font.deriveFont(if (presentation) FontSizePresentationMode else FontSizeEditingMode)
}

private fun Boolean.toLineNumberMode() = if (this) FixedWidth(LineNumberWidth) else None

fun main() = application {
// TODO: Needed to properly composite Compose on top of Swing
// System.setProperty("compose.interop.blending", "true")
Expand Down Expand Up @@ -439,3 +442,8 @@ private fun ExplorerState.setWindowState(windowState: WindowState) {
windowPosY = windowState.position.y.value.toInt()
windowPlacement = windowState.placement
}

private fun CodeStyle.withSettings(indent: Int, lineNumberWidth: Int) =
copy(indent = indent, lineNumberWidth = lineNumberWidth)

private fun CodeStyle.withShowLineNumbers(value: Boolean) = copy(showLineNumbers = value)
44 changes: 36 additions & 8 deletions src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/Settings.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ package dev.romainguy.kotlin.explorer
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Card
import androidx.compose.material3.CardColors
import androidx.compose.material3.CardDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
Expand Down Expand Up @@ -56,6 +55,12 @@ private fun SettingsContent(
) {
val androidHome = remember { mutableStateOf(state.androidHome) }
val kotlinHome = remember { mutableStateOf(state.kotlinHome) }
val indent = remember { mutableStateOf(state.indent.toString()) }
val lineNumberWidth = remember { mutableStateOf(state.lineNumberWidth.toString()) }
val onSaveClick = {
state.saveState(androidHome, kotlinHome, indent, lineNumberWidth)
onDismissRequest()
}

Card(
shape = RoundedCornerShape(8.dp),
Expand All @@ -68,12 +73,10 @@ private fun SettingsContent(
val toolPaths = ToolPaths(state.directory, androidHome.value, kotlinHome.value)
StringSetting("Android home directory: ", androidHome) { toolPaths.isAndroidHomeValid }
StringSetting("Kotlin home directory: ", kotlinHome) { toolPaths.isKotlinHomeValid }
IntSetting("Decompiled Code indent: ", indent, minValue = 2)
IntSetting("Line number column width: ", lineNumberWidth, minValue = 1)

Spacer(modifier = Modifier.height(32.dp))
val onSaveClick = {
state.saveState(androidHome, kotlinHome)
onDismissRequest()
}
Buttons(saveEnabled = toolPaths.isValid, onSaveClick, onDismissRequest)
}
}
Expand Down Expand Up @@ -111,24 +114,48 @@ private fun ColumnScope.Buttons(
private fun ExplorerState.saveState(
androidHome: MutableState<String>,
kotlinHome: MutableState<String>,
indent: MutableState<String>,
lineNumberWidth: MutableState<String>,
) {
this.androidHome = androidHome.value
this.kotlinHome = kotlinHome.value
this.indent = indent.value.toInt()
this.lineNumberWidth = lineNumberWidth.value.toInt()
this.reloadToolPathsFromSettings()
}

@Composable
private fun StringSetting(title: String, state: MutableState<String>, isValid: () -> Boolean) {
SettingRow(title, state.value, { state.value = it }, isValid)
}

@Composable
private fun IntSetting(title: String, state: MutableState<String>, minValue: Int) {

SettingRow(
title,
value = state.value,
onValueChange = {
if (it.toIntOrNull() != null || it.isEmpty()) {
state.value = it
}
},
isValid = { (state.value.toIntOrNull() ?: Int.MIN_VALUE) >= minValue }
)
}

@Composable
private fun SettingRow(title: String, value: String, onValueChange: (String) -> Unit, isValid: () -> Boolean) {
Row {
Text(
title,
modifier = Modifier
.alignByBaseline()
.defaultMinSize(minWidth = 160.dp),
.defaultMinSize(minWidth = 200.dp),
)
TextField(
value = state.value,
onValueChange = { state.value = it },
value = value,
onValueChange = onValueChange,
modifier = Modifier
.alignByBaseline()
.defaultMinSize(minWidth = 360.dp),
Expand All @@ -137,6 +164,7 @@ private fun StringSetting(title: String, state: MutableState<String>, isValid: (
}
}


@Composable
private fun ErrorIcon() {
Icon(
Expand Down
4 changes: 4 additions & 0 deletions src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/State.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ private const val ShowLineNumbers = "SHOW_LINE_NUMBERS"
private const val ShowByteCode = "SHOW_BYTE_CODE"
private const val ShowDex = "SHOW_DEX"
private const val ShowOat = "SHOW_OAT"
private const val Indent = "Indent"
private const val LineNumberWidth = "LINE_NUMBER_WIDTH"
private const val WindowPosX = "WINDOW_X"
private const val WindowPosY = "WINDOW_Y"
private const val WindowWidth = "WINDOW_WIDTH"
Expand All @@ -54,6 +56,8 @@ class ExplorerState {
var showByteCode by BooleanState(ShowByteCode, false)
var showDex by BooleanState(ShowDex, true)
var showOat by BooleanState(ShowOat, true)
var lineNumberWidth by IntState(LineNumberWidth, 4)
var indent by IntState(Indent, 4)
var sourceCode: String = readSourceCode(toolPaths)
var windowWidth by IntState(WindowWidth, 1900)
var windowHeight by IntState(WindowHeight, 1600)
Expand Down
5 changes: 2 additions & 3 deletions src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/code/Code.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package dev.romainguy.kotlin.explorer.code

import dev.romainguy.kotlin.explorer.code.CodeBuilder.LineNumberMode

/**
* A data model representing disassembled code
Expand All @@ -25,13 +24,13 @@ import dev.romainguy.kotlin.explorer.code.CodeBuilder.LineNumberMode
* * Disassembled text with optional line number annotations
* * Jump information for branch instructions
*/
class Code(private val classes: List<Class>, indent: Int, lineNumberMode: LineNumberMode) {
class Code(private val classes: List<Class>, codeStyle: CodeStyle) {
private val jumps: Map<Int, Int>

val text: String

init {
val code = buildCode(indent, lineNumberMode) {
val code = buildCode(codeStyle) {
classes.forEach { clazz ->
startClass(clazz)
clazz.methods.forEach { method ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,8 @@

package dev.romainguy.kotlin.explorer.code

import dev.romainguy.kotlin.explorer.code.CodeBuilder.LineNumberMode
import dev.romainguy.kotlin.explorer.code.CodeBuilder.LineNumberMode.FixedWidth

fun buildCode(indent: Int, lineNumberMode: LineNumberMode, builderAction: CodeBuilder.() -> Unit): CodeBuilder {
return CodeBuilder(indent, lineNumberMode).apply(builderAction)
fun buildCode(codeStyle: CodeStyle, builderAction: CodeBuilder.() -> Unit): CodeBuilder {
return CodeBuilder(codeStyle).apply(builderAction)
}

/**
Expand All @@ -29,10 +26,7 @@ fun buildCode(indent: Int, lineNumberMode: LineNumberMode, builderAction: CodeBu
* This class maintains a state allowing it to build the `jump` table `line-number` list
* that will be used by the UI to display jump markers and line number annotations.
*/
class CodeBuilder(
private val indent: Int,
private val lineNumberMode: LineNumberMode,
) {
class CodeBuilder(private val codeStyle: CodeStyle) {
private var line = 0
private val sb = StringBuilder()
private val jumps = mutableMapOf<Int, Int>()
Expand All @@ -48,7 +42,7 @@ class CodeBuilder(
}

fun startMethod(method: Method) {
sb.append(" ".repeat(indent))
sb.append(" ".repeat(codeStyle.indent))
writeLine(method.header)
}

Expand All @@ -63,15 +57,15 @@ class CodeBuilder(
}

fun writeInstruction(instruction: Instruction) {
sb.append(" ".repeat(indent))
sb.append(" ".repeat(codeStyle.indent))
methodAddresses[instruction.address] = line
if (instruction.jumpAddress != null) {
methodJumps.add(line to instruction.jumpAddress)
}
if (lineNumberMode is FixedWidth) {
if (codeStyle.showLineNumbers) {
val lineNumber = instruction.lineNumber
val prefix = if (lineNumber != null) "$lineNumber:" else " "
sb.append(prefix.padEnd(lineNumberMode.width + 2))
sb.append(prefix.padEnd(codeStyle.lineNumberWidth + 2))
}
writeLine(instruction.code)
}
Expand All @@ -83,10 +77,4 @@ class CodeBuilder(
sb.append('\n')
line++
}

sealed class LineNumberMode {
data object None : LineNumberMode()
class FixedWidth(val width: Int) : LineNumberMode()
// todo: Maybe add an AutoWidth mode that derives the width from the max line number.
}
}
23 changes: 23 additions & 0 deletions src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/code/CodeStyle.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright (C) 2024 Romain Guy
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package dev.romainguy.kotlin.explorer.code

data class CodeStyle(
val indent: Int,
val showLineNumbers: Boolean,
val lineNumberWidth: Int,
)
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@

package dev.romainguy.kotlin.explorer.code

import dev.romainguy.kotlin.explorer.code.CodeBuilder.LineNumberMode
import dev.romainguy.kotlin.explorer.code.CodeBuilder.LineNumberMode.FixedWidth
import dev.romainguy.kotlin.explorer.code.CodeBuilder.LineNumberMode.None
import dev.romainguy.kotlin.explorer.code.CodeContent.*
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea
import java.awt.BasicStroke
Expand All @@ -31,8 +28,7 @@ import javax.swing.event.CaretListener

open class CodeTextArea(
presentationMode: Boolean = false,
private val indent: Int = 4,
lineNumberMode: LineNumberMode = FixedWidth(4),
codeStyle: CodeStyle,
) : RSyntaxTextArea() {
private var code: Code? = null
private var jumpOffsets: JumpOffsets? = null
Expand All @@ -45,19 +41,30 @@ open class CodeTextArea(
repaint()
}

var lineNumberMode = lineNumberMode
var codeStyle = codeStyle
set(value) {
val changed = value != field
field = value
val line = getLineOfOffset(caretPosition)
updateContent()
caretPosition = getLineStartOffset(line)
caretUpdate(line)
if (changed) {
updatePreservingCaretLine()
}
}

init {
addCaretListener(::caretUpdate)
}


private fun updatePreservingCaretLine() {
val line = getLineOfOffset(caretPosition)
val oldText = text
updateContent()
if (oldText != text) {
caretPosition = getLineStartOffset(line)
caretUpdate(line)
}
}

fun setContent(value: CodeContent) {
content = value
updateContent()
Expand All @@ -68,7 +75,7 @@ open class CodeTextArea(
when (val content = content) {
is Empty -> text = ""
is Error -> text = content.errorText
is Success -> code = Code(content.classes, indent, lineNumberMode).also {
is Success -> code = Code(content.classes, codeStyle).also {
val text = it.text
if (text != this.text) {
this.text = text
Expand All @@ -95,18 +102,12 @@ open class CodeTextArea(
val y1 = (bounds1.y + lineHeight / 2).toInt()

val delta = jump.dst - getLineStartOffset(getLineOfOffset(jump.dst))
val showLineNumbers = lineNumberMode !is None
val endPadding = if (showLineNumbers && delta < 4) 2 else padding
val endPadding = if (codeStyle.showLineNumbers && delta < 4) 2 else padding

val x2 = bounds2.x.toInt() - endPadding
val y2 = (bounds2.y + lineHeight / 2).toInt()

val x0 = if (showLineNumbers) {
modelToView2D(minOf(4, jump.dst - 4)).x.toInt() + padding
} else {
modelToView2D(4).x.toInt()
modelToView2D(4).x.toInt()
}
val x0 = modelToView2D(maxOf(codeStyle.indent * 2 - 4, 1)).x.toInt()

val g2 = g as Graphics2D
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
Expand Down

0 comments on commit 4ba91f3

Please sign in to comment.