Skip to content

Commit

Permalink
Support Jump Indicators on OAT View (#26)
Browse files Browse the repository at this point in the history
Notes:
* DexTextArea & OatTextArea are trivial, but I think it results in a cleaner project.
* The `oat` package currently contains only OatTextArea, but it would make sense to move the `oat` parsing there too.
  • Loading branch information
alonalbert authored May 3, 2024
1 parent f51da53 commit 35aae2a
Show file tree
Hide file tree
Showing 7 changed files with 219 additions and 135 deletions.
130 changes: 130 additions & 0 deletions src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/CodeTextArea.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* Copyright (C) 2023 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

import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea
import java.awt.Graphics
import java.awt.Graphics2D
import javax.swing.event.CaretEvent
import javax.swing.event.CaretListener

open class CodeTextArea(
private val explorerState: ExplorerState,
jumpRegex: Regex,
addressedRegex: Regex,
private val lineNumberRegex: Regex?,
) : RSyntaxTextArea() {
private val jumpDetector = JumpDetector(jumpRegex, addressedRegex)
private var jumpOffsets: JumpOffsets? = null
private var fullText = ""


init {
addCaretListener(::caretUpdate)
}

final override fun addCaretListener(listener: CaretListener?) {
super.addCaretListener(listener)
}

override fun setText(text: String) {
fullText = text
refreshText()
}

fun refreshText() {
val saveCaret = caretPosition
super.setText(fullText.takeIf { explorerState.showLineNumbers } ?: fullText.removeLineLumbers())
caretPosition = saveCaret
}

override fun paintComponent(g: Graphics?) {
super.paintComponent(g)
jumpOffsets?.let { jump ->
val padding = 6

val bounds1 = modelToView2D(jump.src)
val bounds2 = modelToView2D(jump.dst)

val x1 = bounds1.x.toInt() - padding
val y1 = (bounds1.y + lineHeight / 2).toInt()

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

val x0 = modelToView2D(2).x.toInt()
val g2 = g as Graphics2D
g2.drawLine(x1, y1, x0, y1)
g2.drawLine(x0, y1, x0, y2)
g2.drawLine(x0, y2, x2, y2)
}
}

private fun caretUpdate(event: CaretEvent) {
val oldJumpOffsets = jumpOffsets
jumpOffsets = null
val srcLine = getLineOfOffset(event.dot)
var start = getLineStartOffset(srcLine)
var end = getLineEndOffset(srcLine)

val caretLine = document.getText(start, end - start).trimEnd()
val jump = jumpDetector.detectJump(caretLine)
if (jump != null) {
val srcOffset = start + caretLine.countPadding()

var dstLine = srcLine + jump.direction
while (dstLine in 0..<lineCount) {
start = getLineStartOffset(dstLine)
end = getLineEndOffset(dstLine)
val line = document.getText(start, end - start).trimEnd()

val addressed = jumpDetector.detectAddressed(line) ?: break
if (addressed == jump.address) {
val dstOffset = start + line.countPadding()
jumpOffsets = JumpOffsets(srcOffset, dstOffset)
}

dstLine += jump.direction
}
}

if (jumpOffsets != oldJumpOffsets) {
repaint()
}
}

private fun String.removeLineLumbers() =
lineNumberRegex?.replace(this) {
val (start, end) = it.groupValues.drop(1)
" ".repeat(start.length) + end
} ?: this

private data class JumpOffsets(val src: Int, val dst: Int)

private class Jump(val address: String, val direction: Int)

private class JumpDetector(private val jumpRegex: Regex, private val addressedRegex: Regex) {
fun detectJump(line: String): Jump? {
val match = jumpRegex.matchEntire(line) ?: return null
return Jump(match.getValue("address"), if (match.getValue("direction") == "+") 1 else -1)
}

fun detectAddressed(line: String) = addressedRegex.matchEntire(line)?.getValue("address")
}
}

private fun String.countPadding() = indexOfFirst { it != ' ' }
128 changes: 0 additions & 128 deletions src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/DexTextArea.kt

This file was deleted.

14 changes: 11 additions & 3 deletions src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/KotlinExplorer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.*
import dev.romainguy.kotlin.explorer.Shortcut.Ctrl
import dev.romainguy.kotlin.explorer.Shortcut.CtrlShift
import dev.romainguy.kotlin.explorer.dex.DexTextArea
import dev.romainguy.kotlin.explorer.oat.OatTextArea
import kotlinx.coroutines.launch
import org.fife.rsta.ui.search.FindDialog
import org.fife.rsta.ui.search.SearchEvent
Expand Down Expand Up @@ -116,7 +118,7 @@ private fun FrameWindowScope.KotlinExplorer(

val sourceTextArea = remember { sourceTextArea(focusTracker, explorerState) }
val dexTextArea = remember { dexTextArea(explorerState, focusTracker) }
val oatTextArea = remember { oatTextArea(focusTracker) }
val oatTextArea = remember { oatTextArea(explorerState, focusTracker) }

val findDialog = remember { FindDialog(window, searchListener).apply { searchContext.searchWrap = true } }
var showSettings by remember { mutableStateOf(!explorerState.toolPaths.isValid) }
Expand Down Expand Up @@ -239,8 +241,8 @@ private fun dexTextArea(explorerState: ExplorerState, focusTracker: FocusListene
}
}

private fun oatTextArea(focusTracker: FocusListener): RSyntaxTextArea {
return RSyntaxTextArea().apply {
private fun oatTextArea(explorerState: ExplorerState, focusTracker: FocusListener): RSyntaxTextArea {
return OatTextArea(explorerState).apply {
configureSyntaxTextArea(SyntaxConstants.SYNTAX_STYLE_NONE)
addFocusListener(focusTracker)
}
Expand Down Expand Up @@ -405,6 +407,12 @@ private fun RSyntaxTextArea.setFont(explorerState: ExplorerState) {
font = font.deriveFont(if (presentation) FontSizePresentationMode else FontSizeEditingMode)
}

private fun updateTextArea(textArea: RSyntaxTextArea, text: String) {
val position = textArea.caretPosition
textArea.text = text
textArea.caretPosition = minOf(position, textArea.document.length)
}

fun main() = application {
val explorerState = remember { ExplorerState() }

Expand Down
21 changes: 21 additions & 0 deletions src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/Regex.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright (C) 2023 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

fun MatchResult.getValue(group: String): String {
return groups[group]?.value ?: throw IllegalStateException("Value of $group not found in $value")
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package dev.romainguy.kotlin.explorer.dex

import dev.romainguy.kotlin.explorer.BuiltInKotlinClass
import dev.romainguy.kotlin.explorer.consumeUntil
import dev.romainguy.kotlin.explorer.getValue

private val PositionRegex = Regex("^\\s*0x(?<address>[0-9a-f]+) line=(?<line>\\d+)$")

Expand Down Expand Up @@ -103,10 +104,6 @@ internal class DexDumpParser(text: String) {
private class DexInstruction(val address: String, val code: String)
}

private fun MatchResult.getValue(group: String): String {
return groups[group]?.value ?: throw IllegalStateException("Value of $group not found in $value")
}

private fun String.getClassName() =
getValue(ClassName)
.removePrefix("L")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright (C) 2023 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.dex

import dev.romainguy.kotlin.explorer.CodeTextArea
import dev.romainguy.kotlin.explorer.ExplorerState

private val DexJumpRegex =
Regex("^.{9}[0-9a-fA-F]{4}: .+(?<address>[0-9a-fA-F]{4}) // (?<direction>[+-])[0-9a-fA-F]{4}$")
private val DexAddressedRegex = Regex("^.{9}(?<address>[0-9a-fA-F]{4}): .+$")
private val DexLineNumberRegex = Regex("^(?<lineNumber> +\\d+: )([0-9a-f]{4}: )")

class DexTextArea(explorerState: ExplorerState) :
CodeTextArea(explorerState, DexJumpRegex, DexAddressedRegex, DexLineNumberRegex)

Loading

0 comments on commit 35aae2a

Please sign in to comment.