diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/action/PreprocessorFileJumpAction.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/action/PreprocessorFileJumpAction.kt index 6b72997..81ae2b4 100644 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/action/PreprocessorFileJumpAction.kt +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/action/PreprocessorFileJumpAction.kt @@ -42,7 +42,7 @@ 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) @@ -50,18 +50,18 @@ class PreprocessorFileJumpAction : DumbAwareAction() { 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 @@ -83,18 +83,20 @@ class PreprocessorFileJumpAction : DumbAwareAction() { }.show() } - private fun getSourceSetFrom(path: List): SourceSetFile? { + private fun getSourceSetFrom(path: List, mainVersion: String, rootDirectory: Path): SourceSetFile? { if (path.size < 4) { return null } - // A path in the format of src//// + // The main file path in the format of src//// if (path[0].toString() == "src") { return SourceSetFile( path[1].toString(), path[2].toString(), path.subList(3, path.size).joinToPath(), null, + mainVersion, + rootDirectory, ) } @@ -102,7 +104,7 @@ class PreprocessorFileJumpAction : DumbAwareAction() { return null } - // A path in the format of `versions//build/preprocessed////` + // A generated preprocessed path in the format of `versions//build/preprocessed////` if (path[0].toString() == "versions" && path[2].toString() == "build" && path[3].toString() == "preprocessed" @@ -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//src////` + 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, ) } diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/SourceSetFile.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/SourceSetFile.kt index 988fa02..9f14ae1 100644 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/SourceSetFile.kt +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/SourceSetFile.kt @@ -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//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.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" + } + } diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/SourceSetFileDialog.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/SourceSetFileDialog.kt index 8b16d47..a0850f6 100644 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/SourceSetFileDialog.kt +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/SourceSetFileDialog.kt @@ -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, + project: Project, + sourceFilesUnsorted: List, 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 { 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//src/ directory.").apply { + font = font.deriveFont(Font.ITALIC, 12f) + foreground = JBColor.GRAY + }, BorderLayout.CENTER) + panel.add(JBScrollPane(list), BorderLayout.SOUTH) return panel }