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

Support Jump Indicators on OAT View #26

Merged
merged 1 commit into from
May 3, 2024
Merged
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
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 @@ -35,6 +35,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 @@ -115,7 +117,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 @@ -204,8 +206,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 @@ -366,6 +368,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