Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -42,26 +42,26 @@ class PreprocessorFileJumpAction : DumbAwareAction() {

val projectPath = currentlyEditingFile.relativeToOrNull(rootDirectory)?.toList()
?: return warning(project, "Current file not in project root")
val currentSourceSetFile = getSourceSetFrom(projectPath)
val currentSourceSetFile = getSourceSetFrom(projectPath, mainVersion, rootDirectory)
?: return warning(project, "File does not seem to be a preprocessor source or generated file")

val allVersions = identifyVersionDirectories(rootDirectory)
if (allVersions.size < 2) {
return warning(project, "Could not find any preprocessed source sets. Make sure to build your project")
}

val targets = allVersions.map { currentSourceSetFile.copy(version = it) }
.filter { it.version != mainVersion } // The preprocessed sources are not generated for the main project
val targets = allVersions.map { currentSourceSetFile.copy(subVersion = it) }
.filter { it.subVersion != mainVersion } // The preprocessed sources are not generated for the main project
val ideView = LangDataKeys.IDE_VIEW.getData(e.dataContext)
?: return warning(project, "Could not find IDE view")

val caret = editor.caretModel.currentCaret.visualPosition
SourceSetFileDialog(project, mainVersion, targets) { selected ->
SourceSetFileDialog(project, targets) { selected ->
val virtualFile = VfsUtil.findFile(rootDirectory.resolve(selected.toRelativePath()), true)
if (virtualFile == null) {
warning(
project,
"Could not find file for version ${selected.version ?: mainVersion} on disk. Try building your project"
"Could not find file for version ${selected.displayVersion} on disk. Try building your project"
)

return@SourceSetFileDialog
Expand All @@ -83,26 +83,28 @@ class PreprocessorFileJumpAction : DumbAwareAction() {
}.show()
}

private fun getSourceSetFrom(path: List<Path>): SourceSetFile? {
private fun getSourceSetFrom(path: List<Path>, mainVersion: String, rootDirectory: Path): SourceSetFile? {
if (path.size < 4) {
return null
}

// A path in the format of src/<sourceset>/<language>/<package>/<class>
// The main file path in the format of src/<sourceset>/<language>/<package>/<class>
if (path[0].toString() == "src") {
return SourceSetFile(
path[1].toString(),
path[2].toString(),
path.subList(3, path.size).joinToPath(),
null,
mainVersion,
rootDirectory,
)
}

if (path.size < 7) {
return null
}

// A path in the format of `versions/<version>/build/preprocessed/<sourceset>/<language>/<package>/<class>`
// A generated preprocessed path in the format of `versions/<version>/build/preprocessed/<sourceset>/<language>/<package>/<class>`
if (path[0].toString() == "versions" &&
path[2].toString() == "build" &&
path[3].toString() == "preprocessed"
Expand All @@ -112,6 +114,22 @@ class PreprocessorFileJumpAction : DumbAwareAction() {
path[5].toString(),
path.subList(6, path.size).joinToPath(),
path[1].toString(),
mainVersion,
rootDirectory,
)
}

// An override file path in the format of `versions/<version>/src/<sourceset>/<language>/<package>/<class>`
if (path[0].toString() == "versions" &&
path[2].toString() == "src"
) {
return SourceSetFile(
path[3].toString(),
path[4].toString(),
path.subList(5, path.size).joinToPath(),
path[1].toString(),
mainVersion,
rootDirectory,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,50 @@ data class SourceSetFile(
val sourceSetName: String,
val language: String,
val classPath: Path,
val version: String?,
val subVersion: String?, // If null, this is the mainVersion in project/src/
private val mainVersion: String,
private val rootDirectory: Path,
) {
// Refers to the path of a possible, non-generated, overriding source file in versions/<subVersion>/src/
private val overridePath: Path? = subVersion?.let {
Path.of("versions", it, "src", sourceSetName, language).resolve(classPath)
}

val isNonGenerated = this.subVersion == null ||
overridePath?.let { rootDirectory.resolve(it).toFile().exists() } == true

fun toRelativePath(): Path {
return if (version == null) {
return if (subVersion == null) {
Path.of("src", sourceSetName, language).resolve(classPath)
} else if (isNonGenerated) {
overridePath!!
} else {
Path.of("versions", version, "build", "preprocessed", sourceSetName, language).resolve(classPath)
Path.of("versions", subVersion, "build", "preprocessed", sourceSetName, language).resolve(classPath)
}
}


val displayVersion = subVersion ?: mainVersion

// Used to sort entries as 1.8.9 will order before 1.12.2 otherwise
val versionInt = displayVersion.split('-').let { platform ->
// Convert semantic version to the preprocessor int: 1.21.2 -> 12102
fun List<String>.getOrZero(index: Int) = getOrNull(index)?.toIntOrNull() ?: 0
val semVer = platform[0].split('.')
semVer.getOrZero(0) * 10000 + semVer.getOrZero(1) * 100 + semVer.getOrZero(2)
}

// Simpler search key used to streamline keyboard navigation via search, 1.21.2-fabric -> 12102fabric
private val simpleVersion = "$versionInt${displayVersion.split('-')[1]}"

override fun toString(): String {
val displayFile = classPath.last()
val srcMark = if (subVersion == null) "(main)"
else if (isNonGenerated) "(override)"
else ""

// e.g. [12102fabric] | 1.21.2-fabric | MyClass.java (main)
return "[$simpleVersion] | $displayVersion | $displayFile $srcMark"
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,66 +4,111 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.DialogWrapper
import com.intellij.ui.CollectionListModel
import com.intellij.ui.DocumentAdapter
import com.intellij.ui.JBColor
import com.intellij.ui.SearchTextField
import com.intellij.ui.components.JBList
import com.intellij.ui.components.JBScrollPane
import java.awt.BorderLayout
import java.awt.Component
import java.io.File
import java.awt.Font
import java.awt.GridLayout
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import javax.swing.DefaultListCellRenderer
import javax.swing.JComponent
import javax.swing.JList
import javax.swing.JLabel
import javax.swing.JPanel
import javax.swing.ListCellRenderer
import javax.swing.ListSelectionModel
import javax.swing.event.DocumentEvent

class SourceSetFileDialog(
private val project: Project,
private val mainVersion: String,
private val sourceFiles: List<SourceSetFile>,
project: Project,
sourceFilesUnsorted: List<SourceSetFile>,
private val onFileChosen: (SourceSetFile) -> Unit
) : DialogWrapper(project) {

private val sourceFiles = sourceFilesUnsorted.sortedBy { it.versionInt }

private val listModel = CollectionListModel(sourceFiles)
private val list = JBList(listModel).apply {
selectionMode = ListSelectionModel.SINGLE_SELECTION
cellRenderer = object : DefaultListCellRenderer() {
override fun getListCellRendererComponent(
list: JList<*>?, value: Any?, index: Int,
isSelected: Boolean, cellHasFocus: Boolean
): Component {
val item = value as SourceSetFile
val display = item.version ?: mainVersion
return super.getListCellRendererComponent(
list,
"${display}${File.separator}${item.classPath}",
index,
isSelected,
cellHasFocus
)
cellRenderer = ListCellRenderer<Any> { list, value, index, isSelected, cellHasFocus ->
// Use DefaultListCellRenderer to get selection colors etc
fun String.label() = DefaultListCellRenderer().getListCellRendererComponent(
list,
this,
index,
isSelected,
cellHasFocus
).apply {
// Further differentiate preprocessed generated files with font style
if ((value as SourceSetFile).isNonGenerated) {
font = font.deriveFont(Font.BOLD)
}
}

JPanel(GridLayout(1, 2)).apply {
val stringParts = value.toString().split("|")
add(JPanel(GridLayout(1, 2)).apply {
add(" ${stringParts[0]}".label())
add("| ${stringParts[1]}".label())
})
add("| ${stringParts[2]}".label())
}
}

// Double click also triggers doOKAction()
addMouseListener(object : MouseAdapter() {
private var lastClickTime = 0L
private var lastSelectedIndex = -1

override fun mouseClicked(e: MouseEvent?) {
if (e?.button != MouseEvent.BUTTON1) return

val clickTime = System.currentTimeMillis()
if (selectedIndex == lastSelectedIndex && clickTime - lastClickTime < 1000) { // 1 second threshold
doOKAction()
}
lastClickTime = clickTime
lastSelectedIndex = selectedIndex
}
})
}
private val search = SearchTextField()

init {
title = "Select Preprocessed Source File"
init()
}

// Focus the search field when the dialog is first opened, streamlines keyboard navigation
override fun getPreferredFocusedComponent(): JComponent? = search

override fun createCenterPanel(): JComponent {
val panel = JPanel(BorderLayout())
val search = SearchTextField()

search.addDocumentListener(object : DocumentAdapter() {
override fun textChanged(e: DocumentEvent) {
val filter = search.text.lowercase()
listModel.replaceAll(sourceFiles.filter {
it.toRelativePath().toString().lowercase().contains(filter)
it.toString().lowercase().contains(filter)
})

if (filter.isEmpty() || listModel.isEmpty) {
list.setSelectedValue(null, false)
} else {
// Improve keyboard navigation by auto-selecting the first result
list.setSelectedValue(listModel.getElementAt(0), false)
}
}
})

panel.add(search, BorderLayout.NORTH)
panel.add(JBScrollPane(list), BorderLayout.CENTER)
panel.add(JLabel(" (override) files are, non-generated, override files present in the versions/<version>/src/ directory.").apply {
font = font.deriveFont(Font.ITALIC, 12f)
foreground = JBColor.GRAY
}, BorderLayout.CENTER)
panel.add(JBScrollPane(list), BorderLayout.SOUTH)
return panel
}

Expand Down
Loading