Skip to content

Commit

Permalink
Sync Lines Between Panels When Possible
Browse files Browse the repository at this point in the history
Not all users would want this so it's controlled by a menu item.
  • Loading branch information
alonalbert committed May 9, 2024
1 parent 95259b8 commit 6d0b1fb
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 27 deletions.
39 changes: 25 additions & 14 deletions src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/KotlinExplorer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ 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.input.key.Key.Companion.S
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.style.TextAlign.Companion.Center
import androidx.compose.ui.unit.DpSize
Expand Down Expand Up @@ -131,17 +132,22 @@ private class UiState(val explorerState: ExplorerState, window: ComposeWindow) {
}
}

val sourceTextArea = sourceTextArea(focusTracker, explorerState).apply { requestFocusInWindow() }
val byteCodeTextArea = byteCodeTextArea(explorerState, focusTracker)
val dexTextArea = dexTextArea(explorerState, focusTracker)
val sourceTextArea: SourceTextArea = sourceTextArea(focusTracker, explorerState).apply { requestFocusInWindow() }
val byteCodeTextArea = byteCodeTextArea(explorerState, focusTracker, sourceTextArea)
val dexTextArea = dexTextArea(explorerState, focusTracker, sourceTextArea)
val oatTextArea = oatTextArea(explorerState, focusTracker)

val codeTextAreas = listOf(byteCodeTextArea, dexTextArea, oatTextArea)

val findDialog = FindDialog(window, searchListener).apply { searchContext.searchWrap = true }
var showSettings by DialogState(!explorerState.toolPaths.isValid)

val updatePresentationMode: (Boolean) -> Unit = {
listOf(dexTextArea, oatTextArea).forEach { area -> area.presentationMode = it }
listOf(byteCodeTextArea, dexTextArea, oatTextArea).forEach { area -> area.presentationMode = it }
}
val updateSyncLinesEnabled: (Boolean) -> Unit = {
listOf(byteCodeTextArea, dexTextArea).forEach { area -> area.isSyncLinesEnabled = it }
sourceTextArea.isSyncLinesEnabled = it
}

val updateShowLineNumbers: (Boolean) -> Unit = {
Expand Down Expand Up @@ -169,6 +175,8 @@ private fun FrameWindowScope.KotlinExplorer(
) {
val uiState = remember { UiState(explorerState, window) }

uiState.sourceTextArea.addCodeTextAreas(uiState.byteCodeTextArea, uiState.dexTextArea)

val sourcePanel: @Composable () -> Unit =
{ SourcePanel(uiState.sourceTextArea, explorerState, uiState.showSettings) }
val byteCodePanel: @Composable () -> Unit =
Expand All @@ -193,6 +201,7 @@ private fun FrameWindowScope.KotlinExplorer(
{ panels = explorerState.getPanels(sourcePanel, byteCodePanel, dexPanel, oatPanel) },
uiState.updateShowLineNumbers,
uiState.updatePresentationMode,
uiState.updateSyncLinesEnabled,
)

if (uiState.showSettings) {
Expand Down Expand Up @@ -322,30 +331,31 @@ private fun Title(text: String) {
)
}

private fun sourceTextArea(focusTracker: FocusListener, explorerState: ExplorerState): RSyntaxTextArea {
return RSyntaxTextArea().apply {
private fun sourceTextArea(focusTracker: FocusListener, explorerState: ExplorerState): SourceTextArea {
return SourceTextArea(explorerState.syncLines).apply {
configureSyntaxTextArea(SyntaxConstants.SYNTAX_STYLE_KOTLIN, focusTracker)
SwingUtilities.invokeLater { requestFocusInWindow() }
document.addDocumentListener(DocumentChangeListener { explorerState.sourceCode = text })
}
}

private fun byteCodeTextArea(state: ExplorerState, focusTracker: FocusListener) =
codeTextArea(state, focusTracker)
private fun byteCodeTextArea(state: ExplorerState, focusTracker: FocusListener, sourceTextArea: SourceTextArea) =
codeTextArea(state, focusTracker, sourceTextArea = sourceTextArea)

private fun dexTextArea(state: ExplorerState, focusTracker: FocusListener) =
codeTextArea(state, focusTracker)
private fun dexTextArea(state: ExplorerState, focusTracker: FocusListener, sourceTextArea: SourceTextArea) =
codeTextArea(state, focusTracker, sourceTextArea = sourceTextArea)

private fun oatTextArea(state: ExplorerState, focusTracker: FocusListener) =
codeTextArea(state, focusTracker, hasLineNumbers = false)

private fun codeTextArea(
state: ExplorerState,
focusTracker: FocusListener,
hasLineNumbers: Boolean = true
hasLineNumbers: Boolean = true,
sourceTextArea: SourceTextArea? = null,
): CodeTextArea {
val codeStyle = CodeStyle(state.indent, state.showLineNumbers && hasLineNumbers, state.lineNumberWidth)
return CodeTextArea(state.presentationMode, codeStyle).apply {
return CodeTextArea(state.presentationMode, codeStyle, state.syncLines, sourceTextArea).apply {
configureSyntaxTextArea(SyntaxConstants.SYNTAX_STYLE_NONE, focusTracker)
}
}
Expand All @@ -365,6 +375,7 @@ private fun FrameWindowScope.MainMenu(
onPanelsUpdated: () -> Unit,
onShowLineNumberChanged: (Boolean) -> Unit,
onPresentationModeChanged: (Boolean) -> Unit,
onSyncLinesChanged: (Boolean) -> Unit,
) {
val scope = rememberCoroutineScope()

Expand Down Expand Up @@ -409,6 +420,7 @@ private fun FrameWindowScope.MainMenu(
MenuCheckboxItem("Show Line Numbers", CtrlShift(L), explorerState::showLineNumbers) {
onShowLineNumberChanged(it)
}
MenuCheckboxItem("Sync lines", Ctrl(S), explorerState::syncLines, onSyncLinesChanged)
Separator()
MenuCheckboxItem("Show Logs", Ctrl(L), explorerState::showLogs)
Separator()
Expand Down Expand Up @@ -442,7 +454,6 @@ private fun RSyntaxTextArea.configureSyntaxTextArea(syntaxStyle: String, focusTr
tabsEmulated = true
tabSize = 4
applyTheme(this)
currentLineHighlightColor = java.awt.Color.decode("#F5F8FF")
addFocusListener(focusTracker)
}

Expand Down Expand Up @@ -526,4 +537,4 @@ private fun ExplorerState.setWindowState(windowState: WindowState) {
private fun CodeStyle.withSettings(indent: Int, lineNumberWidth: Int) =
copy(indent = indent, lineNumberWidth = lineNumberWidth)

private fun CodeStyle.withShowLineNumbers(value: Boolean) = copy(showLineNumbers = value)
private fun CodeStyle.withShowLineNumbers(value: Boolean) = copy(showLineNumbers = value)
49 changes: 49 additions & 0 deletions src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/SourceTextArea.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2024 The Android Open Source Project
*
* 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 dev.romainguy.kotlin.explorer.code.CodeTextArea
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent

class SourceTextArea(var isSyncLinesEnabled: Boolean) : RSyntaxTextArea() {
private val codeTextAreas = mutableListOf<CodeTextArea>()

init {
addMouseListener(object : MouseAdapter() {
override fun mouseClicked(event: MouseEvent) {
if (isSyncLinesEnabled) {
codeTextAreas.forEach {
it.gotoSourceLine(getLineOfOffset(viewToModel2D(event.point)))
}
}
}
})
}

fun addCodeTextAreas(vararg codeTextAreas: CodeTextArea) {
this.codeTextAreas.addAll(codeTextAreas)
}

fun gotoLine(src: CodeTextArea, line: Int) {
caretPosition = getLineStartOffset(line.coerceIn(0 until lineCount))
centerCaretInView()
// Sync other `CodeTextArea` to same line as the `src` sent
codeTextAreas.filter { it !== src }.forEach { it.gotoSourceLine(line) }
}
}
1 change: 1 addition & 0 deletions src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/State.kt
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class ExplorerState {
var showDex by BooleanState(ShowDex, true)
var showOat by BooleanState(ShowOat, true)
var showLogs by mutableStateOf(false)
var syncLines by mutableStateOf(false)
var lineNumberWidth by IntState(LineNumberWidth, 4)
var indent by IntState(Indent, 4)
var sourceCode: String = readSourceCode(toolPaths)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toComposeImageBitmap
import java.awt.Component
import java.awt.GraphicsEnvironment
import java.awt.Point
import java.awt.image.BufferedImage
import javax.swing.JTextArea
import javax.swing.JViewport

/**
* A [SwingPanel] that supports a Dialog rendered over it
Expand Down Expand Up @@ -69,6 +72,12 @@ fun <T : Component> DialogSupportingSwingPanel(
}
}

fun JTextArea.centerCaretInView() {
val viewport = parent as? JViewport ?: return
val linePos = modelToView2D(caretPosition).bounds.centerY.toInt()
viewport.viewPosition = Point(0, maxOf(0, linePos - viewport.height / 2))
}

private fun Component.getScreenShot(): BufferedImage? {
if (width == 0 || height == 0) {
return null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,27 @@

package dev.romainguy.kotlin.explorer.code

import dev.romainguy.kotlin.explorer.SourceTextArea
import dev.romainguy.kotlin.explorer.centerCaretInView
import dev.romainguy.kotlin.explorer.code.CodeContent.*
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea
import java.awt.BasicStroke
import java.awt.Graphics
import java.awt.Graphics2D
import java.awt.RenderingHints
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.awt.geom.GeneralPath
import javax.swing.event.CaretEvent
import javax.swing.event.CaretListener

open class CodeTextArea(
class CodeTextArea(
presentationMode: Boolean = false,
codeStyle: CodeStyle,
var isSyncLinesEnabled: Boolean,
private val sourceTextArea: SourceTextArea?,
) : RSyntaxTextArea() {
private var code: Code? = null
private var jumpOffsets: JumpOffsets? = null

private var content: CodeContent = Empty

var presentationMode = presentationMode
Expand All @@ -52,9 +56,31 @@ open class CodeTextArea(

init {
addCaretListener(::caretUpdate)

if (sourceTextArea != null) {
addMouseListener(object : MouseAdapter() {
override fun mouseClicked(event: MouseEvent) {
println(size)
val codeLine = getLineOfOffset(viewToModel2D(event.point))
val line = code?.getSourceLine(codeLine) ?: return
sourceTextArea.gotoLine(this@CodeTextArea, line - 1)
}
})
}
}


fun setContent(value: CodeContent) {
content = value
updateContent()
}

fun gotoSourceLine(sourceLine: Int) {
val line = code?.getCodeLine(sourceLine + 1) ?: return
caretPosition = getLineStartOffset(line.coerceIn(0 until lineCount))
centerCaretInView()
}

private fun updatePreservingCaretLine() {
val line = getLineOfOffset(caretPosition)
val oldText = text
Expand All @@ -65,11 +91,6 @@ open class CodeTextArea(
}
}

fun setContent(value: CodeContent) {
content = value
updateContent()
}

private fun updateContent() {
val position = caretPosition
code = null
Expand All @@ -86,10 +107,6 @@ open class CodeTextArea(
caretPosition = minOf(position, document.length)
}

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

override fun paintComponent(g: Graphics?) {
super.paintComponent(g)
jumpOffsets?.let { jump ->
Expand Down
2 changes: 1 addition & 1 deletion src/jvmMain/resources/themes/kotlin_explorer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<background color="ffffff" />
<caret color="000000" />
<selection fg="ffffff" bg="526da5" />
<currentLineHighlight color="f5f8fe" fade="false" />
<currentLineHighlight color="f2f6fe" fade="false" />
<marginLine fg="b0b4b9" />
<markAllHighlight color="edebfc" />
<markOccurrencesHighlight color="edebfc" border="false" />
Expand Down

0 comments on commit 6d0b1fb

Please sign in to comment.