From 13eebb53e66378ac27ce073cdbefdc78c15bcb53 Mon Sep 17 00:00:00 2001 From: Traben Date: Mon, 29 Sep 2025 22:52:06 +1000 Subject: [PATCH 01/19] create plugin settings - usages in later commits --- .../config/PluginConfigurable.kt | 115 ++++++++++++++++++ .../intelliprocessor/config/PluginSettings.kt | 39 ++++++ src/main/resources/META-INF/plugin.xml | 2 + 3 files changed, 156 insertions(+) create mode 100644 src/main/kotlin/org/polyfrost/intelliprocessor/config/PluginConfigurable.kt create mode 100644 src/main/kotlin/org/polyfrost/intelliprocessor/config/PluginSettings.kt diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/config/PluginConfigurable.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/config/PluginConfigurable.kt new file mode 100644 index 0000000..24af7bb --- /dev/null +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/config/PluginConfigurable.kt @@ -0,0 +1,115 @@ +package org.polyfrost.intelliprocessor.config + +import com.intellij.openapi.options.Configurable +import javax.swing.BoxLayout +import javax.swing.JCheckBox +import javax.swing.JComponent +import javax.swing.JLabel +import javax.swing.JPanel +import javax.swing.border.EtchedBorder +import javax.swing.border.TitledBorder + +class PluginConfigurable : Configurable { + private lateinit var panel: JPanel + private lateinit var foldInactiveBlocksByDefaultCheckbox: JCheckBox + private lateinit var foldAllBlocksByDefaultCheckbox: JCheckBox + private lateinit var inspectionHighlightNonIndentedNestedIfsCheckbox: JCheckBox + private lateinit var inspectionHighlightCommentsNotMatchingIfIndentsCheckbox: JCheckBox + private lateinit var hideUnmatchedVersionsCheckbox: JCheckBox + + + override fun getDisplayName(): String = "IntelliProcessor" + + override fun createComponent(): JComponent { + + // Setup components + + fun J.tooltip(str: String): J = apply { toolTipText = str } + + foldInactiveBlocksByDefaultCheckbox = JCheckBox("Fold inactive preprocessor blocks by default") + .tooltip("Automatically folds preprocessor blocks that are conditionally inactive. (E.G. 'MC>=1.20' blocks in a 1.19 file)") + + foldAllBlocksByDefaultCheckbox = JCheckBox("Fold all preprocessor blocks by default").apply { + addChangeListener { event -> + // Disable the "fold inactive blocks" option if "fold all blocks" is enabled + foldInactiveBlocksByDefaultCheckbox.isEnabled = !(event.source as JCheckBox).isSelected + } + } + + inspectionHighlightNonIndentedNestedIfsCheckbox = + JCheckBox("Highlight non-indented nested \"if\" preprocessor directives (Code clarity)") + .tooltip( + "Highlights nested \"if\" preprocessor directives that are not indented more than their enclosing preprocessor block.\n" + + "\nThis does not break preprocessing, but can help improve code clarity by visually indicating the nested structure of preprocessor blocks." + ) + + inspectionHighlightCommentsNotMatchingIfIndentsCheckbox = + JCheckBox("Highlight preprocessor comments not matching their \"if\"'s indent (Code clarity)") + .tooltip( + "Highlights preprocessor comments whose indent does not match the indent of the corresponding \"if\" directive.\n" + + "\nThis does not break preprocessing, but can help improve code clarity by visually linking preprocessor comments to their corresponding \"if\" directives." + ) + + hideUnmatchedVersionsCheckbox = JCheckBox("Hide results that do not meet preprocessor conditions at the caret") + .tooltip("Hides version results in the 'Jump To Pre-Processed File' dialog that do not match the current file's preprocessor conditions found at the caret position.") + + + // Arrange components + + fun titledBlock(str: String, block: JPanel.() -> Unit): JPanel = JPanel().apply { + border = TitledBorder(EtchedBorder(),str) + layout = BoxLayout(this, BoxLayout.Y_AXIS) + block() + } + + panel = JPanel() + + panel.apply { + layout = BoxLayout(this, BoxLayout.Y_AXIS) + + add(titledBlock("Folding") { + add(foldAllBlocksByDefaultCheckbox) + add(foldInactiveBlocksByDefaultCheckbox) + }) + + add(titledBlock("Inspection Highlighting") { + add(inspectionHighlightNonIndentedNestedIfsCheckbox) + add(inspectionHighlightCommentsNotMatchingIfIndentsCheckbox) + }) + + add(titledBlock("Jump To Pre-Processed File Action") { + add(hideUnmatchedVersionsCheckbox) + }) + + add(titledBlock("Info") { + add(JLabel("The keybinds can be configured from: Keymap > Plugins > IntelliProcessor")) + }) + } + + reset() + return panel + } + + override fun isModified(): Boolean = + foldAllBlocksByDefaultCheckbox.isSelected != PluginSettings.instance.foldAllBlocksByDefault + || foldInactiveBlocksByDefaultCheckbox.isSelected != PluginSettings.instance.foldInactiveBlocksByDefault + || inspectionHighlightNonIndentedNestedIfsCheckbox.isSelected != PluginSettings.instance.inspectionHighlightNonIndentedNestedIfs + || inspectionHighlightCommentsNotMatchingIfIndentsCheckbox.isSelected != PluginSettings.instance.inspectionHighlightCommentsNotMatchingIfIndents + || hideUnmatchedVersionsCheckbox.isSelected != PluginSettings.instance.hideUnmatchedVersions + + override fun apply() { + PluginSettings.instance.foldAllBlocksByDefault = foldAllBlocksByDefaultCheckbox.isSelected + PluginSettings.instance.foldInactiveBlocksByDefault = foldInactiveBlocksByDefaultCheckbox.isSelected + PluginSettings.instance.inspectionHighlightNonIndentedNestedIfs = inspectionHighlightNonIndentedNestedIfsCheckbox.isSelected + PluginSettings.instance.inspectionHighlightCommentsNotMatchingIfIndents = inspectionHighlightCommentsNotMatchingIfIndentsCheckbox.isSelected + PluginSettings.instance.hideUnmatchedVersions = hideUnmatchedVersionsCheckbox.isSelected + } + + override fun reset() { + foldAllBlocksByDefaultCheckbox.isSelected = PluginSettings.instance.foldAllBlocksByDefault + foldInactiveBlocksByDefaultCheckbox.isSelected = PluginSettings.instance.foldInactiveBlocksByDefault + inspectionHighlightNonIndentedNestedIfsCheckbox.isSelected = PluginSettings.instance.inspectionHighlightNonIndentedNestedIfs + inspectionHighlightCommentsNotMatchingIfIndentsCheckbox.isSelected = PluginSettings.instance.inspectionHighlightCommentsNotMatchingIfIndents + hideUnmatchedVersionsCheckbox.isSelected = PluginSettings.instance.hideUnmatchedVersions + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/config/PluginSettings.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/config/PluginSettings.kt new file mode 100644 index 0000000..25f9bb1 --- /dev/null +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/config/PluginSettings.kt @@ -0,0 +1,39 @@ +package org.polyfrost.intelliprocessor.config + +import com.intellij.openapi.components.PersistentStateComponent +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.State +import com.intellij.openapi.components.Storage +import com.intellij.openapi.components.service + +@State(name = "IntelliProcessor", storages = [Storage("IntelliProcessor.xml")]) +@Service +class PluginSettings : PersistentStateComponent { + var foldAllBlocksByDefault: Boolean = false + var foldInactiveBlocksByDefault: Boolean = true + var inspectionHighlightNonIndentedNestedIfs: Boolean = true + var inspectionHighlightCommentsNotMatchingIfIndents: Boolean = true + var hideUnmatchedVersions: Boolean = false + + override fun getState(): PluginSettings = this + + override fun loadState(state: PluginSettings) { + this.foldAllBlocksByDefault = state.foldAllBlocksByDefault + this.foldInactiveBlocksByDefault = state.foldInactiveBlocksByDefault + this.inspectionHighlightNonIndentedNestedIfs = state.inspectionHighlightNonIndentedNestedIfs + this.inspectionHighlightCommentsNotMatchingIfIndents = state.inspectionHighlightCommentsNotMatchingIfIndents + this.hideUnmatchedVersions = state.hideUnmatchedVersions + } + + companion object { + val instance: PluginSettings + get() = service() + + // Helper to modify settings and correctly persist changes + fun modify(action: PluginSettings.() -> Unit) { + val settings = PluginSettings().also { it.loadState(instance) } + settings.action() + instance.loadState(settings) // Pass changes back to instance via loadState() to persist + } + } +} \ No newline at end of file diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 099a2a8..d76c53f 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -15,6 +15,8 @@ + + From 307819a7dd27cfdb738f0c16f16442bc01a1b35b Mon Sep 17 00:00:00 2001 From: Traben Date: Mon, 29 Sep 2025 22:56:32 +1000 Subject: [PATCH 02/19] merge `Versions` and `MainProject` functionality into new general `PreprocessorVersion` class usages in later commits (this commit was cherry-picked after the fact) --- .../intelliprocessor/utils/MainProject.kt | 28 --------- .../utils/PreprocessorVersion.kt | 60 +++++++++++++++++++ .../intelliprocessor/utils/Versions.kt | 18 ------ 3 files changed, 60 insertions(+), 46 deletions(-) delete mode 100644 src/main/kotlin/org/polyfrost/intelliprocessor/utils/MainProject.kt create mode 100644 src/main/kotlin/org/polyfrost/intelliprocessor/utils/PreprocessorVersion.kt delete mode 100644 src/main/kotlin/org/polyfrost/intelliprocessor/utils/Versions.kt diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/MainProject.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/MainProject.kt deleted file mode 100644 index 37f4160..0000000 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/MainProject.kt +++ /dev/null @@ -1,28 +0,0 @@ -package org.polyfrost.intelliprocessor.utils - -import com.intellij.psi.PsiFile -import java.nio.file.Files - -object MainProject { - - fun get(file: PsiFile): String? { - val moduleDir = findModuleDirForFile(file) ?: run { - println("Module directory could not be found for file: ${file.virtualFile?.path}") - return null - } - - val versionFile = moduleDir.toPath().resolve("versions/mainProject") - if (!Files.exists(versionFile)) { - println("Main project version file does not exist at: $versionFile") - return null - } - - return Files.readString(versionFile).trim() - } - - fun comparable(file: PsiFile): Int? { - val version = get(file) ?: return null - return Versions.makeComparable(version) - } - -} diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/PreprocessorVersion.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/PreprocessorVersion.kt new file mode 100644 index 0000000..b7c098d --- /dev/null +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/PreprocessorVersion.kt @@ -0,0 +1,60 @@ +package org.polyfrost.intelliprocessor.utils + +import com.intellij.openapi.vfs.toNioPathOrNull +import com.intellij.psi.PsiFile +import java.nio.file.Files +import kotlin.collections.toList +import kotlin.io.path.relativeTo + +// Represents a preprocessor comparable minecraft version along with its loader (e.g., fabric, forge). +// Accessed as properties on string-ified preprocessor versions and PsiFiles. +class PreprocessorVersion private constructor(val mc: Int, val loader: String) { + companion object { + val NULL = PreprocessorVersion(0, "null") + + val String.preprocessorVersion: PreprocessorVersion? get() { + val int = makeComparable(this) ?: return null + val loader = split("-").getOrNull(1) ?: return null + return PreprocessorVersion(int, loader) + } + + val PsiFile.preprocessorVersion: PreprocessorVersion? get() { + val version = versionStringOfFile ?: return null + return version.preprocessorVersion + } + + val PsiFile.versionStringOfFile: String? get() { + val rootDirectory = findModuleDirForFile(this)?.toPath() ?: return null + val relPath = virtualFile.toNioPathOrNull()?.relativeTo(rootDirectory)?.toList() ?: return null + if (relPath[0].toString() != "versions") return preprocessorMainVersion + return relPath[1].toString() + } + + val PsiFile.preprocessorMainVersion: String? get() { + val moduleDir = findModuleDirForFile(this) ?: run { + println("Module directory could not be found for file: ${virtualFile?.path}") + return null + } + + val versionFile = moduleDir.toPath().resolve("versions/mainProject") + if (!Files.exists(versionFile)) { + println("Main project version file does not exist at: $versionFile") + return null + } + + return Files.readString(versionFile).trim() + } + + private val regex = "(?\\d+)\\.(?\\d+)(?:\\.(?\\d+))?".toRegex() + private fun makeComparable(version: String): Int? { + val match = regex.find(version) ?: return null + val groups = match.groups + + val major = groups["major"]?.value?.toInt() ?: return null + val minor = groups["minor"]?.value?.toInt() ?: return null + val patch = groups["patch"]?.value?.toInt() ?: 0 + + return major * 10000 + minor * 100 + patch + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/Versions.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/Versions.kt deleted file mode 100644 index 9a6556a..0000000 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/Versions.kt +++ /dev/null @@ -1,18 +0,0 @@ -package org.polyfrost.intelliprocessor.utils - -object Versions { - - private val regex = "(?\\d+)\\.(?\\d+)(?:\\.(?\\d+))?".toRegex() - - fun makeComparable(version: String): Int? { - val match = regex.find(version) ?: return null - val groups = match.groups - - val major = groups["major"]?.value?.toInt() ?: return null - val minor = groups["minor"]?.value?.toInt() ?: return null - val patch = groups["patch"]?.value?.toInt() ?: 0 - - return major * 10000 + minor * 100 + patch - } - -} From cb39d3cc1b946f5072d6a8bc6d1d66928c82cd18 Mon Sep 17 00:00:00 2001 From: Traben Date: Mon, 29 Sep 2025 22:58:07 +1000 Subject: [PATCH 03/19] add general utils for retrieving directives usages in later commits (this commit was cherry-picked after the fact) --- .../org/polyfrost/intelliprocessor/utils/utils.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/utils.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/utils.kt index 70d7b2a..0c42da2 100644 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/utils.kt +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/utils.kt @@ -1,6 +1,7 @@ package org.polyfrost.intelliprocessor.utils import com.intellij.codeInsight.completion.* +import com.intellij.lang.LanguageCommenters import com.intellij.notification.NotificationGroupManager import com.intellij.notification.NotificationType import com.intellij.openapi.editor.Editor @@ -8,6 +9,7 @@ import com.intellij.openapi.project.Project import com.intellij.patterns.ElementPattern import com.intellij.psi.* import com.intellij.psi.impl.source.tree.LeafPsiElement +import com.intellij.psi.util.PsiTreeUtil import com.intellij.util.ProcessingContext import java.nio.file.Path @@ -52,3 +54,13 @@ fun warning( .createNotification(content, NotificationType.WARNING) .notify(project) } + +fun PsiElement.directivePrefix(): String? { + return (LanguageCommenters.INSTANCE.forLanguage(language).lineCommentPrefix ?: return null) + "#" + } + +fun PsiElement.allPreprocessorDirectiveComments(): List { + val directivePrefix = directivePrefix() ?: return emptyList() + return PsiTreeUtil.findChildrenOfType(this, PsiComment::class.java) + .filter { it.text.startsWith(directivePrefix) } +} \ No newline at end of file From b605366a5402714fe4ac1fd8e8c379a4ae9c497b Mon Sep 17 00:00:00 2001 From: Traben Date: Mon, 29 Sep 2025 23:05:19 +1000 Subject: [PATCH 04/19] fixed and moved preprocessor condition checking logic into its own class for more generic use outside `PreprocessorNewLineHandler` fixes: - added loader condition parsing e.g. `//#if FABRIC` - added parsing for multiple chained expressions e.g. `//#if FABRIC && MC>=1` - fixed the broken `.prevSibling` iterating that did not always properly navigate to previous directive Psi's usages in later commits (this commit was cherry-picked after the fact) --- .../editor/PreprocessorNewLineHandler.kt | 92 +-------- .../utils/PreprocessorConditions.kt | 186 ++++++++++++++++++ 2 files changed, 191 insertions(+), 87 deletions(-) create mode 100644 src/main/kotlin/org/polyfrost/intelliprocessor/utils/PreprocessorConditions.kt diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/editor/PreprocessorNewLineHandler.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/editor/PreprocessorNewLineHandler.kt index 49b73e7..1839bdf 100644 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/editor/PreprocessorNewLineHandler.kt +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/editor/PreprocessorNewLineHandler.kt @@ -8,11 +8,10 @@ import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.actionSystem.EditorActionHandler import com.intellij.openapi.project.DumbAware import com.intellij.openapi.util.Ref -import com.intellij.psi.PsiComment -import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import org.polyfrost.intelliprocessor.ALLOWED_FILE_TYPES import org.polyfrost.intelliprocessor.utils.* +import org.polyfrost.intelliprocessor.utils.PreprocessorVersion.Companion.preprocessorVersion import java.util.Locale class PreprocessorNewLineHandler : EnterHandlerDelegateAdapter(), DumbAware { @@ -43,13 +42,13 @@ class PreprocessorNewLineHandler : EnterHandlerDelegateAdapter(), DumbAware { return Result.DefaultForceIndent } - val conditionals = findEnclosingConditionalBlock(comment) - if (conditionals.isEmpty()) { + val conditions = PreprocessorConditions.findEnclosingConditionsOrNull(comment, file) + if (conditions == null) { return Result.Continue } - val currentVersion = MainProject.comparable(file) - if (currentVersion != null && isInsideActiveBlock(conditionals, currentVersion)) { + val currentVersion = file.preprocessorVersion + if (currentVersion != null && conditions.testVersion(currentVersion)) { return Result.Continue } @@ -68,85 +67,4 @@ class PreprocessorNewLineHandler : EnterHandlerDelegateAdapter(), DumbAware { return Result.Stop } - private fun isInsideActiveBlock( - conditionals: List, - currentVersion: Int - ): Boolean { - for (directive in conditionals) { - when (directive) { - is PreprocessorDirective.If, is PreprocessorDirective.ElseIf -> { - val condition = (directive as ConditionContainingDirective).condition - if (evaluateCondition(condition, currentVersion)) { - return true - } - } - - is PreprocessorDirective.IfDef -> return true // TODO - is PreprocessorDirective.Else -> return true - is PreprocessorDirective.EndIf -> break - } - } - return false - } - - private fun evaluateCondition(condition: String, currentVersion: Int): Boolean { - if (!condition.startsWith("MC")) { - return true // Non-MC conditions are always considered "active" - } - - val match = Regex("""MC\s*(==|!=|<=|>=|<|>)\s*(\S+)""").find(condition) ?: return false - val (operator, rhsStr) = match.destructured - val rhs = Versions.makeComparable(rhsStr) ?: return false - - return when (operator) { - "==" -> currentVersion == rhs - "!=" -> currentVersion != rhs - "<=" -> currentVersion <= rhs - ">=" -> currentVersion >= rhs - "<" -> currentVersion < rhs - ">" -> currentVersion > rhs - else -> false - } - } - - private fun findEnclosingConditionalBlock(comment: PsiComment): List { - val block = mutableListOf() - var sibling: PsiElement? = comment - var nesting = 0 - - while (sibling != null) { - if (sibling is PsiComment) { - val directive = sibling.parseDirective() - if (directive == null) { - sibling = sibling.prevSibling - continue - } - - when (directive) { - is PreprocessorDirective.EndIf -> { - nesting++ - } - - is PreprocessorDirective.If, is PreprocessorDirective.IfDef -> { - if (nesting == 0) { - block.add(0, directive) - } else { - nesting-- - } - } - - is PreprocessorDirective.ElseIf, is PreprocessorDirective.Else -> { - if (nesting == 0) { - block.add(0, directive) - } - } - } - } - - sibling = sibling.prevSibling - } - - return block - } - } diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/PreprocessorConditions.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/PreprocessorConditions.kt new file mode 100644 index 0000000..c04997d --- /dev/null +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/PreprocessorConditions.kt @@ -0,0 +1,186 @@ +package org.polyfrost.intelliprocessor.utils + +import com.intellij.psi.PsiComment +import com.intellij.psi.PsiFile +import com.intellij.psi.util.startOffset + + +class PreprocessorConditions private constructor( + private val trueConditions: List, + private val falseConditions: List +) { + // Unknown / Unresolvable expressions will evaluate as null, and return failureResult + fun testVersion(version: PreprocessorVersion, failureResult: Boolean = true): Boolean { + // Check all conditions that should be true + for (directive in trueConditions) { + when (directive) { + is PreprocessorDirective.If, is PreprocessorDirective.ElseIf -> { + val condition = (directive as ConditionContainingDirective).condition + if (!(evaluateBooleanConditions(condition, version) ?: return failureResult)) { + return false + } + } + is PreprocessorDirective.IfDef -> continue // TODO + is PreprocessorDirective.Else -> continue + is PreprocessorDirective.EndIf -> break + } + } + + // Then check all conditions that should be false + for (directive in falseConditions) { + when (directive) { + is PreprocessorDirective.If, is PreprocessorDirective.ElseIf -> { + val condition = (directive as ConditionContainingDirective).condition + if (evaluateBooleanConditions(condition, version) ?: return failureResult) { + return false + } + } + is PreprocessorDirective.IfDef -> return false // TODO + is PreprocessorDirective.Else -> return false // Shouldn't ever occur + is PreprocessorDirective.EndIf -> break + } + } + + return true + } + + companion object { + + // Only providing file and an offset point within it, we find the nearest preceding directive comment and use that as the starting point + fun findEnclosingConditionsOrNull(offset: Int, file: PsiFile): PreprocessorConditions? { + val directives = file.allPreprocessorDirectiveComments() + val previousComment = directives.lastOrNull { it.startOffset <= offset } ?: return null + return findEnclosingConditionsOrNull(previousComment, directives) + } + + fun findEnclosingConditionsOrNull(comment: PsiComment, file: PsiFile): PreprocessorConditions? = + findEnclosingConditionsOrNull(comment, file.allPreprocessorDirectiveComments()) + + fun findEnclosingConditionsOrNull( + comment: PsiComment, + allDirectives: List + ): PreprocessorConditions? { + var index = allDirectives.indexOfFirst { it === comment } + if (index == -1) return null + + val trueBlock = mutableListOf() + val falseBlock = mutableListOf() + var sibling: PsiComment? = comment + var nesting = 0 + + // Tracks whether our reverse iteration has passed an else/elseif directive + // If so, we add further directives to the falseBlock instead of the trueBlock, until we leave the initial if block + var elseNesting = false + + fun prev(): PsiComment? = allDirectives.getOrNull(--index) + + while (sibling != null) { + val directive = sibling.parseDirective() + if (directive == null) { + sibling = prev() + continue + } + + when (directive) { + is PreprocessorDirective.EndIf -> { + nesting++ + } + + is PreprocessorDirective.If, is PreprocessorDirective.IfDef -> { + if (nesting == 0) { + if (elseNesting) { + falseBlock.add(0, directive) + } else { + trueBlock.add(0, directive) + } + elseNesting = false + } else { + nesting-- + } + } + + is PreprocessorDirective.ElseIf, is PreprocessorDirective.Else -> { + if (nesting == 0) { + if (elseNesting) { + falseBlock.add(0, directive) + } else { + trueBlock.add(0, directive) + } + elseNesting = true + } + } + } + sibling = prev() + } + + if (trueBlock.isEmpty() && falseBlock.isEmpty()) return null + + return PreprocessorConditions(trueBlock, falseBlock) + } + + private fun logAndNull(str: String): Boolean? { + println(str) + return null + } + + private val BOOLEAN_SPLITTER = Regex("""\s*(&&|\|\||[^&|]+)\s*""") + + private fun evaluateBooleanConditions(conditions: String, currentVersion: PreprocessorVersion): Boolean? { + // Multiple conditions separated by && or || + if (conditions.contains("||") || conditions.contains("&&")) { + val tokens = BOOLEAN_SPLITTER.findAll(conditions).map { it.groupValues[1] }.toList() + var result = evaluateCondition(tokens[0], currentVersion) + ?: return logAndNull("Could not evaluate condition: ${tokens[0]}") + var i = 1 + while (i < tokens.size) { + val op = tokens[i] + val next = evaluateCondition(tokens[i + 1], currentVersion) + ?: return logAndNull("Could not evaluate condition: ${tokens[i + 1]}") + result = when (op) { + "&&" -> result && next + "||" -> result || next + else -> return logAndNull("op wasn't && or || in: $conditions") // Shouldn't occur + } + i += 2 + } + return result + } + + // Single condition + return evaluateCondition(conditions, currentVersion) + } + + private fun evaluateCondition(conditionRaw: String, currentVersion: PreprocessorVersion): Boolean? { + val condition = conditionRaw.trim() + if (!condition.startsWith("MC")) { + + // Check simple loader conditions + val lower = condition.lowercase().removePrefix("!") + if (lower.contains("fabric") || lower.contains("forge")) { + return condition.startsWith("!") != (currentVersion.loader == lower) + } + + return logAndNull("Could not evaluate unknown condition: $condition") // Unknown conditions are always considered "active" + } + + val match = Regex("""MC\s*(==|!=|<=|>=|<|>)\s*(\S+)""").find(condition) + ?: return logAndNull("Could not evaluate (MC ?? Number) condition: $condition") + val (operator, rhsStr) = match.destructured + + + val rhs = rhsStr.toIntOrNull() + ?: return logAndNull("Could not evaluate version number in MC condition: $condition") + + val compare = currentVersion.mc + return when (operator) { + "==" -> compare == rhs + "!=" -> compare != rhs + "<=" -> compare <= rhs + ">=" -> compare >= rhs + "<" -> compare < rhs + ">" -> compare > rhs + else -> logAndNull("Could not evaluate MC condition operator: $condition") + } + } + } +} \ No newline at end of file From f6c14c508836af9841bd8465015b037debc8626d Mon Sep 17 00:00:00 2001 From: Traben Date: Mon, 29 Sep 2025 23:07:02 +1000 Subject: [PATCH 05/19] added option to allow preprocessor blocks to be folded by default depending on their conditions (or always) --- .../editor/PreprocessorFolding.kt | 59 +++++++++++++++---- 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/editor/PreprocessorFolding.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/editor/PreprocessorFolding.kt index 2718090..b21e658 100644 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/editor/PreprocessorFolding.kt +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/editor/PreprocessorFolding.kt @@ -9,7 +9,12 @@ import com.intellij.openapi.project.DumbAware import com.intellij.openapi.util.TextRange import com.intellij.psi.PsiComment import com.intellij.psi.PsiElement -import com.intellij.psi.util.PsiTreeUtil +import org.polyfrost.intelliprocessor.config.PluginSettings +import org.polyfrost.intelliprocessor.utils.PreprocessorConditions +import org.polyfrost.intelliprocessor.utils.PreprocessorVersion +import org.polyfrost.intelliprocessor.utils.PreprocessorVersion.Companion.preprocessorVersion +import org.polyfrost.intelliprocessor.utils.allPreprocessorDirectiveComments +import org.polyfrost.intelliprocessor.utils.directivePrefix class PreprocessorFolding : FoldingBuilderEx(), DumbAware { @@ -26,12 +31,16 @@ class PreprocessorFolding : FoldingBuilderEx(), DumbAware { document: Document, quick: Boolean, ): Array { - val descriptors = mutableListOf() - val directivePrefix = (LanguageCommenters.INSTANCE.forLanguage(root.language).lineCommentPrefix ?: return emptyArray()) + "#" - val allDirectives = PsiTreeUtil.findChildrenOfType(root, PsiComment::class.java) - .filter { it.text.startsWith(directivePrefix) } + // Required to allow the "fold inactive blocks by default" feature + // Disabled for quick mode so we don't run all the condition checks on the fly + val preprocessorVersion = + if (quick || !PluginSettings.instance.foldInactiveBlocksByDefault) null + else root.containingFile.preprocessorVersion + val descriptors = mutableListOf() + val directivePrefix = root.directivePrefix() + val allDirectives = root.allPreprocessorDirectiveComments() val stack = ArrayDeque() for (directive in allDirectives) { @@ -42,16 +51,18 @@ class PreprocessorFolding : FoldingBuilderEx(), DumbAware { stack.addLast(directive) } - text.startsWith(directivePrefix + "else") || text.startsWith(directivePrefix + "elseif") -> { + text.startsWith(directivePrefix + "else") -> { // elseif caught too val startDirective = stack.removeLastOrNull() if (startDirective != null) { val commentLine = document.getLineNumber(directive.textOffset) if (commentLine > 0) { val prevLineEnd = document.getLineEndOffset(commentLine - 1) descriptors.add( - FoldingDescriptor( - startDirective, - TextRange(startDirective.textRange.startOffset, prevLineEnd) + fold(startDirective, + startDirective.textRange.startOffset, + prevLineEnd, + preprocessorVersion, + allDirectives ) ) stack.addLast(directive) @@ -63,9 +74,11 @@ class PreprocessorFolding : FoldingBuilderEx(), DumbAware { val startDirective = stack.removeLastOrNull() if (startDirective != null) { descriptors.add( - FoldingDescriptor( - startDirective, - TextRange(startDirective.textRange.startOffset, directive.textRange.endOffset) + fold(startDirective, + startDirective.textRange.startOffset, + directive.textRange.endOffset, + preprocessorVersion, + allDirectives ) ) } @@ -76,8 +89,28 @@ class PreprocessorFolding : FoldingBuilderEx(), DumbAware { return descriptors.toTypedArray() } + private fun fold(element: PsiComment, startOffset: Int, endOffset: Int, thisVersion: PreprocessorVersion?, allDirectives: List): FoldingDescriptor { + if (thisVersion == null || PluginSettings.instance.foldAllBlocksByDefault) { + return FoldingDescriptor(element, TextRange(startOffset, endOffset)) + } + + val shouldFold = PreprocessorConditions.findEnclosingConditionsOrNull(element, allDirectives)?.let{ + !it.testVersion(thisVersion) + } + + return FoldingDescriptor( + element.node, + TextRange(startOffset, endOffset), + null, + emptySet(), + false, + null, + shouldFold + ) + } + override fun isCollapsedByDefault(node: ASTNode): Boolean { - return false + return PluginSettings.instance.foldAllBlocksByDefault } } From eccf09545ef34331d1f70d8d804f69dfdd75e7bf Mon Sep 17 00:00:00 2001 From: Traben Date: Mon, 29 Sep 2025 23:07:43 +1000 Subject: [PATCH 06/19] use new version object --- .../intelliprocessor/utils/SourceSetFile.kt | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/SourceSetFile.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/SourceSetFile.kt index 9f14ae1..7887eb3 100644 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/SourceSetFile.kt +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/SourceSetFile.kt @@ -1,5 +1,6 @@ package org.polyfrost.intelliprocessor.utils +import org.polyfrost.intelliprocessor.utils.PreprocessorVersion.Companion.preprocessorVersion import java.nio.file.Path data class SourceSetFile( @@ -31,16 +32,7 @@ data class SourceSetFile( 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]}" + val version = displayVersion.preprocessorVersion ?: PreprocessorVersion.NULL override fun toString(): String { val displayFile = classPath.last() @@ -49,7 +41,7 @@ data class SourceSetFile( else "" // e.g. [12102fabric] | 1.21.2-fabric | MyClass.java (main) - return "[$simpleVersion] | $displayVersion | $displayFile $srcMark" + return "[${version.mc}${version.loader}] | $displayVersion | $displayFile $srcMark" } } From dfe9eeb213d9a2563007956884fae4a90eed98cc Mon Sep 17 00:00:00 2001 From: Traben Date: Mon, 29 Sep 2025 23:13:55 +1000 Subject: [PATCH 07/19] improve file jump action and dialog to be able to filter results via whether they met the preprocessor conditions at the caret position of the current file --- .../action/PreprocessorFileJumpAction.kt | 27 +++++- .../intelliprocessor/utils/SourceSetFile.kt | 1 + .../utils/SourceSetFileDialog.kt | 90 ++++++++++++++----- 3 files changed, 95 insertions(+), 23 deletions(-) diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/action/PreprocessorFileJumpAction.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/action/PreprocessorFileJumpAction.kt index 81ae2b4..4c8cc60 100644 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/action/PreprocessorFileJumpAction.kt +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/action/PreprocessorFileJumpAction.kt @@ -3,17 +3,20 @@ package org.polyfrost.intelliprocessor.action import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.LangDataKeys import com.intellij.openapi.actionSystem.PlatformDataKeys +import com.intellij.openapi.editor.Editor import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.fileEditor.TextEditor import com.intellij.openapi.project.DumbAwareAction import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VfsUtil +import com.intellij.psi.PsiFile import com.intellij.psi.PsiManager import org.polyfrost.intelliprocessor.utils.* +import org.polyfrost.intelliprocessor.utils.PreprocessorVersion.Companion.preprocessorMainVersion import java.nio.file.Path import kotlin.io.path.relativeToOrNull -class PreprocessorFileJumpAction : DumbAwareAction() { +open class PreprocessorFileJumpAction : DumbAwareAction() { private companion object { private const val GROUP_ID = "Jump Failure" @@ -32,7 +35,7 @@ class PreprocessorFileJumpAction : DumbAwareAction() { val rootDirectory = findModuleDirForFile(currentPsiFile) ?.toPath() ?: return warning(project, "Could not find module directory for file") - val mainVersion = MainProject.get(currentPsiFile) + val mainVersion = currentPsiFile.preprocessorMainVersion ?: return warning(project, "Could not find mainProject. Is this a preprocessor project?") val currentlyEditingFile = currentPsiFile.virtualFile?.toNioPath() ?: return warning(project, "Could not find file on disk") @@ -56,7 +59,11 @@ class PreprocessorFileJumpAction : DumbAwareAction() { ?: return warning(project, "Could not find IDE view") val caret = editor.caretModel.currentCaret.visualPosition - SourceSetFileDialog(project, targets) { selected -> + + // For if the caret is inside a preprocessor conditional block, test each target version against the conditions + val foundConditionContext = testTargetsAgainstPreprocessorConditions(currentPsiFile, editor, targets) + + SourceSetFileDialog(project, targets, foundConditionContext) { selected -> val virtualFile = VfsUtil.findFile(rootDirectory.resolve(selected.toRelativePath()), true) if (virtualFile == null) { warning( @@ -83,6 +90,20 @@ class PreprocessorFileJumpAction : DumbAwareAction() { }.show() } + // If the caret is inside a preprocessor conditional block, test each target version against the conditions there + private fun testTargetsAgainstPreprocessorConditions( + file: PsiFile, + editor: Editor, + targets: List + ): Boolean { + val selectedPos = editor.caretModel.currentCaret.offset + val conditions = PreprocessorConditions.findEnclosingConditionsOrNull(selectedPos, file) + if (conditions != null) { + targets.forEach { it.metOpeningCondition = conditions.testVersion(it.version) } + } + return conditions != null + } + private fun getSourceSetFrom(path: List, mainVersion: String, rootDirectory: Path): SourceSetFile? { if (path.size < 4) { return null diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/SourceSetFile.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/SourceSetFile.kt index 7887eb3..0590ecf 100644 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/SourceSetFile.kt +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/SourceSetFile.kt @@ -29,6 +29,7 @@ data class SourceSetFile( } } + var metOpeningCondition = true val displayVersion = subVersion ?: mainVersion diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/SourceSetFileDialog.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/SourceSetFileDialog.kt index a0850f6..c6c279c 100644 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/SourceSetFileDialog.kt +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/SourceSetFileDialog.kt @@ -6,9 +6,12 @@ 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.CheckBox import com.intellij.ui.components.JBList import com.intellij.ui.components.JBScrollPane +import org.polyfrost.intelliprocessor.config.PluginSettings import java.awt.BorderLayout +import java.awt.Component import java.awt.Font import java.awt.GridLayout import java.awt.event.MouseAdapter @@ -24,10 +27,12 @@ import javax.swing.event.DocumentEvent class SourceSetFileDialog( project: Project, sourceFilesUnsorted: List, - private val onFileChosen: (SourceSetFile) -> Unit + private val hadConditions: Boolean, + private val onFileChosen: (SourceSetFile) -> Unit, ) : DialogWrapper(project) { - private val sourceFiles = sourceFilesUnsorted.sortedBy { it.versionInt } + // Sort entries via int version as 1.8.9 will order before 1.12.2 otherwise + private val sourceFiles = sourceFilesUnsorted.sortedBy { it.version.mc } private val listModel = CollectionListModel(sourceFiles) private val list = JBList(listModel).apply { @@ -42,8 +47,11 @@ class SourceSetFileDialog( cellHasFocus ).apply { // Further differentiate preprocessed generated files with font style - if ((value as SourceSetFile).isNonGenerated) { - font = font.deriveFont(Font.BOLD) + (value as? SourceSetFile)?.let { + if (it.isNonGenerated) { + font = font.deriveFont(Font.BOLD) + } + if (!it.metOpeningCondition) foreground = JBColor.GRAY } } @@ -89,29 +97,71 @@ class SourceSetFileDialog( search.addDocumentListener(object : DocumentAdapter() { override fun textChanged(e: DocumentEvent) { - val filter = search.text.lowercase() - listModel.replaceAll(sourceFiles.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) - } + filterList() } }) panel.add(search, BorderLayout.NORTH) - 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) + panel.add(JBScrollPane(list), BorderLayout.CENTER) + bottomPanelOrNull()?.let { + panel.add(it,BorderLayout.SOUTH) + } return panel } + private fun bottomPanelOrNull(): JPanel? { + fun String.label(): JLabel = JLabel(this).apply { + font = font.deriveFont(Font.ITALIC, 12f) + foreground = JBColor.GRAY + } + + val belowList = mutableListOf() + if (sourceFiles.any { it.subVersion != null && it.isNonGenerated }) { + belowList.add(" - (override) files are, non-generated, override files present in the versions//src/ directory.".label()) + } + if (!hadConditions) { + belowList.add(" - No preprocessor conditions were found to apply at the caret position to test results with. Showing all.".label()) + } else if (sourceFiles.any { !it.metOpeningCondition }) { + val hide = PluginSettings.instance.hideUnmatchedVersions + val hideText = + " - Results that do not meet the preprocessor conditions at the caret position have been hidden." + val showText = + " - Faded results are those that do not meet the preprocessor conditions at the caret position." + val label = (if (hide) hideText else showText).label() + belowList.add(label) + belowList.add(CheckBox("Hide results that do not meet preprocessor conditions at caret", hide).apply { + addActionListener { + PluginSettings.modify { hideUnmatchedVersions = isSelected } + label.text = if (isSelected) hideText else showText + filterList() + } + }) + filterList() + } + + return if (belowList.isEmpty()) null else JPanel(GridLayout(belowList.size, 1)).apply { + for (below in belowList) { + add(below, BorderLayout.NORTH) + } + } + } + + private fun filterList() { + val filter = search.text.lowercase() + listModel.replaceAll(sourceFiles.filter { + it.toString().lowercase().contains(filter) + // Hide entries that do not meet the opening condition if the setting is enabled + && (!PluginSettings.instance.hideUnmatchedVersions || it.metOpeningCondition) + }) + + 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) + } + } + override fun doOKAction() { val selected = list.selectedValue ?: return onFileChosen(selected) From 4edf40c4ac1888f6c621a7dc345752df7029c9ac Mon Sep 17 00:00:00 2001 From: Traben Date: Mon, 29 Sep 2025 23:16:28 +1000 Subject: [PATCH 08/19] add preprocessor styling syntax highlights: - highlight if directives that are not further indented than their outer containing block - highlight non directives & `//$$` that are not inline with their initial if directive --- .../editor/PreprocessorSyntaxHighlight.kt | 70 ++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/editor/PreprocessorSyntaxHighlight.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/editor/PreprocessorSyntaxHighlight.kt index da1e7d2..68f7546 100644 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/editor/PreprocessorSyntaxHighlight.kt +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/editor/PreprocessorSyntaxHighlight.kt @@ -21,6 +21,7 @@ import com.intellij.psi.PsiRecursiveElementWalkingVisitor import com.intellij.psi.impl.source.tree.PsiCommentImpl import org.polyfrost.intelliprocessor.ALLOWED_FILE_TYPES import org.polyfrost.intelliprocessor.Scope +import org.polyfrost.intelliprocessor.config.PluginSettings import java.util.ArrayDeque import java.util.Locale @@ -65,6 +66,11 @@ class PreprocessorSyntaxHighlight(private val project: Project) : HighlightVisit private lateinit var commenter: Commenter private lateinit var highlighter: SyntaxHighlighter private var stack = ArrayDeque() + private var indentStack = ArrayDeque() + private var indentGetter: (PsiElement) -> Int = { 0 } + + private val doNestedIfIdentWarn get() = PluginSettings.instance.inspectionHighlightNonIndentedNestedIfs + private val doIdentMatchWarn get() = PluginSettings.instance.inspectionHighlightCommentsNotMatchingIfIndents override fun suitableForFile(file: PsiFile): Boolean { return file.fileType.name.uppercase(Locale.ROOT) in ALLOWED_FILE_TYPES @@ -84,6 +90,13 @@ class PreprocessorSyntaxHighlight(private val project: Project) : HighlightVisit this.commenter = LanguageCommenters.INSTANCE.forLanguage(file.language) this.highlighter = SyntaxHighlighterFactory.getSyntaxHighlighter(file.language, file.project, file.virtualFile) this.stack = ArrayDeque() + this.indentStack = ArrayDeque() + indentGetter = { + val offset = it.textOffset + val line = file.fileDocument.getLineNumber(offset) + val lineStart = file.fileDocument.getLineStartOffset(line) + offset - lineStart + } file.accept(object : PsiRecursiveElementWalkingVisitor() { override fun visitElement(element: PsiElement) { visit(element) @@ -112,6 +125,14 @@ class PreprocessorSyntaxHighlight(private val project: Project) : HighlightVisit comment.startsWith("$$") -> { holder.add("$$".toDirectiveHighlight(element, prefixLength)) highlightCodeBlock(element, element.startOffset + prefixLength + 2, comment.drop(2)) + + if (doIdentMatchWarn) { + val indent = indentGetter(element) + val previousIndent = indentStack.peekFirst() + if (indent != (previousIndent ?: -1)) { + warn(element, "\"$$\" line is not indented the same as it's containing block (Code clarity)") + } + } } } } @@ -138,6 +159,21 @@ class PreprocessorSyntaxHighlight(private val project: Project) : HighlightVisit stack.pop() } + if (doNestedIfIdentWarn || doIdentMatchWarn) { + val indent = indentGetter(element) + val previousIndent = indentStack.peekFirst() + if (directive == "if") { + if (doNestedIfIdentWarn && indent <= (previousIndent ?: -1)) { + warn(element, "\"$directive\" is not indented more than it's outer \"if\" block (Code clarity)") + } + indentStack.push(indent) + } else if (directive == "elseif") { + if (doIdentMatchWarn && indent != (previousIndent ?: -1)) { + warn(element, "\"$directive\" is not indented the same as it's starting \"if\" (Code clarity)") + } + } + } + stack.push(Scope.IF) holder.add(directive.toDirectiveHighlight(element, prefixLength)) @@ -165,6 +201,15 @@ class PreprocessorSyntaxHighlight(private val project: Project) : HighlightVisit } private fun handleIfDef(element: PsiCommentImpl, segments: List, prefixLength: Int) { + if (doNestedIfIdentWarn) { + val indent = indentGetter(element) + val previousIndent = indentStack.peekFirst() + if (indent <= (previousIndent ?: -1)) { + warn(element, "\"ifdef\" is not indented more than it's outer \"if\" block (Code clarity)") + } + indentStack.push(indent) + } + stack.push(Scope.IF) holder.add("ifdef".toDirectiveHighlight(element, prefixLength)) @@ -189,6 +234,13 @@ class PreprocessorSyntaxHighlight(private val project: Project) : HighlightVisit fail(element, "\"else\" must follow \"if\" (last in scope: ${previous?.name})") return } + if (doIdentMatchWarn) { + val indent = indentGetter(element) + val previousIndent = indentStack.peekFirst() + if (indent != (previousIndent ?: -1)) { + warn(element, "\"else\" is not indented the same as it's starting \"if\" (Code clarity)") + } + } stack.pop() stack.push(Scope.ELSE) @@ -205,6 +257,14 @@ class PreprocessorSyntaxHighlight(private val project: Project) : HighlightVisit fail(element, "\"endif\" must follow \"if\" or \"else\" (last in scope: ${previous?.name})") return } + if (doIdentMatchWarn) { + val indent = indentGetter(element) + val previousIndent = indentStack.peekFirst() + if (indent != (previousIndent ?: -1)) { + warn(element, "\"endif\" is not indented the same as it's starting \"if\" (Code clarity)") + } + indentStack.pop() + } else if (doNestedIfIdentWarn) indentStack.pop() stack.pop() holder.add("endif".toDirectiveHighlight(element, prefixLength)) @@ -239,8 +299,14 @@ class PreprocessorSyntaxHighlight(private val project: Project) : HighlightVisit } } - private fun fail(element: PsiElement, message: String, eol: Boolean = false) { - val builder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + private fun fail(element: PsiElement, message: String, eol: Boolean = false) = + highlightType(element, message, eol, HighlightInfoType.ERROR) + + private fun warn(element: PsiElement, message: String, eol: Boolean = false) = + highlightType(element, message, eol, HighlightInfoType.WARNING) + + private fun highlightType(element: PsiElement, message: String, eol: Boolean = false, type: HighlightInfoType) { + val builder = HighlightInfo.newHighlightInfo(type) .descriptionAndTooltip(message) if (eol) { From 5916d9ee606bcc83e0dfe8b70342172dfb7158f3 Mon Sep 17 00:00:00 2001 From: Traben Date: Tue, 30 Sep 2025 15:14:51 +1000 Subject: [PATCH 09/19] fix caret position after jump action, also trigger a scroll to the caret position --- .../intelliprocessor/action/PreprocessorFileJumpAction.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/action/PreprocessorFileJumpAction.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/action/PreprocessorFileJumpAction.kt index 4c8cc60..68dc2e0 100644 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/action/PreprocessorFileJumpAction.kt +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/action/PreprocessorFileJumpAction.kt @@ -4,6 +4,7 @@ import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.LangDataKeys import com.intellij.openapi.actionSystem.PlatformDataKeys import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.ScrollType import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.fileEditor.TextEditor import com.intellij.openapi.project.DumbAwareAction @@ -58,7 +59,7 @@ open class PreprocessorFileJumpAction : DumbAwareAction() { val ideView = LangDataKeys.IDE_VIEW.getData(e.dataContext) ?: return warning(project, "Could not find IDE view") - val caret = editor.caretModel.currentCaret.visualPosition + val caret = editor.caretModel.currentCaret.offset // For if the caret is inside a preprocessor conditional block, test each target version against the conditions val foundConditionContext = testTargetsAgainstPreprocessorConditions(currentPsiFile, editor, targets) @@ -83,7 +84,8 @@ open class PreprocessorFileJumpAction : DumbAwareAction() { ideView.selectElement(psiFile) val newEditor = FileEditorManager.getInstance(project).getSelectedEditor(virtualFile) if (newEditor is TextEditor) { - newEditor.editor.caretModel.moveToVisualPosition(caret) + newEditor.editor.caretModel.moveToOffset(caret) + newEditor.editor.scrollingModel.scrollToCaret(ScrollType.CENTER) } else { warning(project, "Could not set cursor for non-text file") } From 7de227f8455d7b2cb8724cc811f8b895d3989669 Mon Sep 17 00:00:00 2001 From: Traben Date: Tue, 30 Sep 2025 16:43:27 +1000 Subject: [PATCH 10/19] fix newline `//$$` only being placed in the line after directives --- .../editor/PreprocessorNewLineHandler.kt | 18 ++++-------------- .../utils/PreprocessorConditions.kt | 5 +---- 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/editor/PreprocessorNewLineHandler.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/editor/PreprocessorNewLineHandler.kt index 1839bdf..debe628 100644 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/editor/PreprocessorNewLineHandler.kt +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/editor/PreprocessorNewLineHandler.kt @@ -34,21 +34,11 @@ class PreprocessorNewLineHandler : EnterHandlerDelegateAdapter(), DumbAware { } val caretPos = caretOffset.get() - val psiAtOffset = file.findElementAt(caretPos) ?: return Result.Continue - val comment = psiAtOffset.containingComment ?: return Result.Continue + val currentVersion = file.preprocessorVersion ?: return Result.Continue + val conditions = PreprocessorConditions.findEnclosingConditionsOrNull(caretPos, file) ?: return Result.Continue - val posInText = caretPos - comment.textRange.startOffset - if (posInText < 4) { - return Result.DefaultForceIndent - } - - val conditions = PreprocessorConditions.findEnclosingConditionsOrNull(comment, file) - if (conditions == null) { - return Result.Continue - } - - val currentVersion = file.preprocessorVersion - if (currentVersion != null && conditions.testVersion(currentVersion)) { + // We are inside a preprocessor block, now check if it is active or not + if (conditions.testVersion(currentVersion)) { return Result.Continue } diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/PreprocessorConditions.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/PreprocessorConditions.kt index c04997d..8f47f18 100644 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/PreprocessorConditions.kt +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/PreprocessorConditions.kt @@ -53,11 +53,8 @@ class PreprocessorConditions private constructor( return findEnclosingConditionsOrNull(previousComment, directives) } - fun findEnclosingConditionsOrNull(comment: PsiComment, file: PsiFile): PreprocessorConditions? = - findEnclosingConditionsOrNull(comment, file.allPreprocessorDirectiveComments()) - fun findEnclosingConditionsOrNull( - comment: PsiComment, + comment: PsiComment, // This comment should only be an already identified directive comment allDirectives: List ): PreprocessorConditions? { var index = allDirectives.indexOfFirst { it === comment } From b0afbd56bdd51c8105b618b59d11e364d8e72a9e Mon Sep 17 00:00:00 2001 From: Traben Date: Tue, 30 Sep 2025 21:10:17 +1000 Subject: [PATCH 11/19] Keyboard navigation: Down arrow focuses into the list from the search field --- .../utils/SourceSetFileDialog.kt | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/SourceSetFileDialog.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/SourceSetFileDialog.kt index c6c279c..bd4efa5 100644 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/SourceSetFileDialog.kt +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/SourceSetFileDialog.kt @@ -14,6 +14,8 @@ import java.awt.BorderLayout import java.awt.Component import java.awt.Font import java.awt.GridLayout +import java.awt.event.KeyAdapter +import java.awt.event.KeyEvent import java.awt.event.MouseAdapter import java.awt.event.MouseEvent import javax.swing.DefaultListCellRenderer @@ -82,7 +84,21 @@ class SourceSetFileDialog( } }) } - private val search = SearchTextField() + + private val search = SearchTextField().apply { + // Keyboard navigation: Down arrow focuses into the list from the search field + textEditor.addKeyListener(object : KeyAdapter() { + override fun keyPressed(e: KeyEvent?) { + if (e?.keyCode == KeyEvent.VK_DOWN || e?.keyCode == KeyEvent.VK_KP_DOWN) { + if (list.selectedValue == null) { + list.selectedIndex = 0 + } + list.requestFocusInWindow() + e.consume() + } + } + }) + } init { title = "Select Preprocessed Source File" From 961e4393d7eed064c2b58ea49a178b17f7c22c24 Mon Sep 17 00:00:00 2001 From: Traben Date: Fri, 3 Oct 2025 15:20:29 +1000 Subject: [PATCH 12/19] alternate highlighting --- .../intelliprocessor/editor/PreprocessorSyntaxHighlight.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/editor/PreprocessorSyntaxHighlight.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/editor/PreprocessorSyntaxHighlight.kt index 68f7546..6722047 100644 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/editor/PreprocessorSyntaxHighlight.kt +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/editor/PreprocessorSyntaxHighlight.kt @@ -303,7 +303,7 @@ class PreprocessorSyntaxHighlight(private val project: Project) : HighlightVisit highlightType(element, message, eol, HighlightInfoType.ERROR) private fun warn(element: PsiElement, message: String, eol: Boolean = false) = - highlightType(element, message, eol, HighlightInfoType.WARNING) + highlightType(element, message, eol, HighlightInfoType.WEAK_WARNING) private fun highlightType(element: PsiElement, message: String, eol: Boolean = false, type: HighlightInfoType) { val builder = HighlightInfo.newHighlightInfo(type) From 0ffe70b9a2aef66d4e9975edaaef8107db43df28 Mon Sep 17 00:00:00 2001 From: Traben Date: Fri, 3 Oct 2025 16:42:47 +1000 Subject: [PATCH 13/19] fix kotlin folding --- .../{ => folding}/PreprocessorFolding.kt | 36 ++++++++++++------- .../editor/folding/PreprocessorFoldingJava.kt | 3 ++ .../folding/PreprocessorFoldingKotlin.kt | 3 ++ src/main/resources/META-INF/java-plugin.xml | 2 +- src/main/resources/META-INF/kotlin-plugin.xml | 8 +---- src/main/resources/META-INF/plugin.xml | 5 +++ 6 files changed, 37 insertions(+), 20 deletions(-) rename src/main/kotlin/org/polyfrost/intelliprocessor/editor/{ => folding}/PreprocessorFolding.kt (77%) create mode 100644 src/main/kotlin/org/polyfrost/intelliprocessor/editor/folding/PreprocessorFoldingJava.kt create mode 100644 src/main/kotlin/org/polyfrost/intelliprocessor/editor/folding/PreprocessorFoldingKotlin.kt diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/editor/PreprocessorFolding.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/editor/folding/PreprocessorFolding.kt similarity index 77% rename from src/main/kotlin/org/polyfrost/intelliprocessor/editor/PreprocessorFolding.kt rename to src/main/kotlin/org/polyfrost/intelliprocessor/editor/folding/PreprocessorFolding.kt index b21e658..f677526 100644 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/editor/PreprocessorFolding.kt +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/editor/folding/PreprocessorFolding.kt @@ -1,4 +1,4 @@ -package org.polyfrost.intelliprocessor.editor +package org.polyfrost.intelliprocessor.editor.folding import com.intellij.lang.ASTNode import com.intellij.lang.LanguageCommenters @@ -16,7 +16,10 @@ import org.polyfrost.intelliprocessor.utils.PreprocessorVersion.Companion.prepro import org.polyfrost.intelliprocessor.utils.allPreprocessorDirectiveComments import org.polyfrost.intelliprocessor.utils.directivePrefix -class PreprocessorFolding : FoldingBuilderEx(), DumbAware { +/** + * Registering the folding builder class only allows 1 class per language, so we will make this abstract and extend it for java and kotlin + */ +abstract class PreprocessorFolding : FoldingBuilderEx(), DumbAware { override fun getPlaceholderText(node: ASTNode): String { val comment = node.psi as? PsiComment ?: return "..." @@ -31,16 +34,16 @@ class PreprocessorFolding : FoldingBuilderEx(), DumbAware { document: Document, quick: Boolean, ): Array { + val descriptors = mutableListOf() // Required to allow the "fold inactive blocks by default" feature // Disabled for quick mode so we don't run all the condition checks on the fly val preprocessorVersion = - if (quick || !PluginSettings.instance.foldInactiveBlocksByDefault) null + if (!PluginSettings.Companion.instance.foldInactiveBlocksByDefault) null else root.containingFile.preprocessorVersion - val descriptors = mutableListOf() val directivePrefix = root.directivePrefix() - val allDirectives = root.allPreprocessorDirectiveComments() + val allDirectives = root.containingFile.allPreprocessorDirectiveComments() val stack = ArrayDeque() for (directive in allDirectives) { @@ -58,7 +61,8 @@ class PreprocessorFolding : FoldingBuilderEx(), DumbAware { if (commentLine > 0) { val prevLineEnd = document.getLineEndOffset(commentLine - 1) descriptors.add( - fold(startDirective, + fold( + startDirective, startDirective.textRange.startOffset, prevLineEnd, preprocessorVersion, @@ -74,7 +78,8 @@ class PreprocessorFolding : FoldingBuilderEx(), DumbAware { val startDirective = stack.removeLastOrNull() if (startDirective != null) { descriptors.add( - fold(startDirective, + fold( + startDirective, startDirective.textRange.startOffset, directive.textRange.endOffset, preprocessorVersion, @@ -89,12 +94,19 @@ class PreprocessorFolding : FoldingBuilderEx(), DumbAware { return descriptors.toTypedArray() } - private fun fold(element: PsiComment, startOffset: Int, endOffset: Int, thisVersion: PreprocessorVersion?, allDirectives: List): FoldingDescriptor { - if (thisVersion == null || PluginSettings.instance.foldAllBlocksByDefault) { + private fun fold( + element: PsiComment, + startOffset: Int, + endOffset: Int, + thisVersion: PreprocessorVersion?, + allDirectives: List + ): FoldingDescriptor { + + if (thisVersion == null || PluginSettings.Companion.instance.foldAllBlocksByDefault) { return FoldingDescriptor(element, TextRange(startOffset, endOffset)) } - val shouldFold = PreprocessorConditions.findEnclosingConditionsOrNull(element, allDirectives)?.let{ + val shouldFold = PreprocessorConditions.Companion.findEnclosingConditionsOrNull(element, allDirectives)?.let { !it.testVersion(thisVersion) } @@ -110,7 +122,7 @@ class PreprocessorFolding : FoldingBuilderEx(), DumbAware { } override fun isCollapsedByDefault(node: ASTNode): Boolean { - return PluginSettings.instance.foldAllBlocksByDefault + return PluginSettings.Companion.instance.foldAllBlocksByDefault } -} +} \ No newline at end of file diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/editor/folding/PreprocessorFoldingJava.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/editor/folding/PreprocessorFoldingJava.kt new file mode 100644 index 0000000..f011a29 --- /dev/null +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/editor/folding/PreprocessorFoldingJava.kt @@ -0,0 +1,3 @@ +package org.polyfrost.intelliprocessor.editor.folding + +class PreprocessorFoldingJava : PreprocessorFolding() \ No newline at end of file diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/editor/folding/PreprocessorFoldingKotlin.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/editor/folding/PreprocessorFoldingKotlin.kt new file mode 100644 index 0000000..b851ce8 --- /dev/null +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/editor/folding/PreprocessorFoldingKotlin.kt @@ -0,0 +1,3 @@ +package org.polyfrost.intelliprocessor.editor.folding + +class PreprocessorFoldingKotlin : PreprocessorFolding() \ No newline at end of file diff --git a/src/main/resources/META-INF/java-plugin.xml b/src/main/resources/META-INF/java-plugin.xml index 9948ecd..71f7a37 100644 --- a/src/main/resources/META-INF/java-plugin.xml +++ b/src/main/resources/META-INF/java-plugin.xml @@ -2,7 +2,7 @@ diff --git a/src/main/resources/META-INF/kotlin-plugin.xml b/src/main/resources/META-INF/kotlin-plugin.xml index 6e8e2d9..06acc68 100644 --- a/src/main/resources/META-INF/kotlin-plugin.xml +++ b/src/main/resources/META-INF/kotlin-plugin.xml @@ -1,14 +1,8 @@ - - - - - IntelliProcessor Polyfrost + + + + + com.intellij.modules.platform com.intellij.modules.java org.jetbrains.kotlin From fdf6a68ebd255b75ffbd980c18d0517dab6edcfa Mon Sep 17 00:00:00 2001 From: Traben Date: Fri, 3 Oct 2025 16:43:22 +1000 Subject: [PATCH 14/19] ensure this util only runs on a psifile --- src/main/kotlin/org/polyfrost/intelliprocessor/utils/utils.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/utils.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/utils.kt index 0c42da2..39a13b6 100644 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/utils.kt +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/utils.kt @@ -59,7 +59,7 @@ fun PsiElement.directivePrefix(): String? { return (LanguageCommenters.INSTANCE.forLanguage(language).lineCommentPrefix ?: return null) + "#" } -fun PsiElement.allPreprocessorDirectiveComments(): List { +fun PsiFile.allPreprocessorDirectiveComments(): List { val directivePrefix = directivePrefix() ?: return emptyList() return PsiTreeUtil.findChildrenOfType(this, PsiComment::class.java) .filter { it.text.startsWith(directivePrefix) } From 3f939657cf584d1373a4deb22f905bc31555829e Mon Sep 17 00:00:00 2001 From: Traben Date: Fri, 3 Oct 2025 16:46:47 +1000 Subject: [PATCH 15/19] support grabbing the preprocessor version for certain additional PsiFile types that don't have backing files --- .../intelliprocessor/utils/PreprocessorVersion.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/PreprocessorVersion.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/PreprocessorVersion.kt index b7c098d..bdcbced 100644 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/PreprocessorVersion.kt +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/PreprocessorVersion.kt @@ -1,5 +1,7 @@ package org.polyfrost.intelliprocessor.utils +import com.intellij.openapi.module.ModuleUtilCore +import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.toNioPathOrNull import com.intellij.psi.PsiFile import java.nio.file.Files @@ -23,9 +25,15 @@ class PreprocessorVersion private constructor(val mc: Int, val loader: String) { return version.preprocessorVersion } + private val extractFromModule = """\b\d+\.\d+(?:\.\d+)?-\w+\b""".toRegex() val PsiFile.versionStringOfFile: String? get() { + val vf: VirtualFile? = virtualFile + if (vf == null) { // No backing file + val module = ModuleUtilCore.findModuleForPsiElement(this) ?: return null + return extractFromModule.find(module.name)?.value ?: preprocessorMainVersion + } val rootDirectory = findModuleDirForFile(this)?.toPath() ?: return null - val relPath = virtualFile.toNioPathOrNull()?.relativeTo(rootDirectory)?.toList() ?: return null + val relPath = vf.toNioPathOrNull()?.relativeTo(rootDirectory)?.toList() ?: return null if (relPath[0].toString() != "versions") return preprocessorMainVersion return relPath[1].toString() } From 39c0aeaedce274bef97a413bb150dd5223cf1409 Mon Sep 17 00:00:00 2001 From: Traben Date: Fri, 3 Oct 2025 16:48:41 +1000 Subject: [PATCH 16/19] no longer used util, prevSibling behaviour has proved inconsistent anyway --- .../org/polyfrost/intelliprocessor/utils/utils.kt | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/utils.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/utils.kt index 39a13b6..3ab6de3 100644 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/utils.kt +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/utils.kt @@ -8,7 +8,6 @@ import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.patterns.ElementPattern import com.intellij.psi.* -import com.intellij.psi.impl.source.tree.LeafPsiElement import com.intellij.psi.util.PsiTreeUtil import com.intellij.util.ProcessingContext import java.nio.file.Path @@ -16,17 +15,6 @@ import java.nio.file.Path val Editor.activeFile: PsiFile? get() = project?.let { project -> PsiDocumentManager.getInstance(project).getPsiFile(this.document) } -val PsiElement.containingComment: PsiComment? - get() = when (this) { - is PsiComment -> this - is PsiWhiteSpace, is PsiPlainText, is LeafPsiElement -> { - this.prevSibling?.takeIf { it is PsiComment } as? PsiComment - ?: this.parent?.takeIf { it is PsiComment } as? PsiComment - } - - else -> parent as? PsiComment - } - fun Iterable.joinToPath(): Path { return reduce { acc, path -> acc.resolve(path) From e1fb704a62ccfc9acafc643f09431a162c5d879e Mon Sep 17 00:00:00 2001 From: Traben Date: Fri, 3 Oct 2025 16:58:43 +1000 Subject: [PATCH 17/19] add setting to disable PreprocessorNewLineHandler --- .../intelliprocessor/config/PluginConfigurable.kt | 12 +++++++++++- .../intelliprocessor/config/PluginSettings.kt | 2 ++ .../editor/PreprocessorNewLineHandler.kt | 6 ++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/config/PluginConfigurable.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/config/PluginConfigurable.kt index 24af7bb..bd6c1fc 100644 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/config/PluginConfigurable.kt +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/config/PluginConfigurable.kt @@ -16,6 +16,7 @@ class PluginConfigurable : Configurable { private lateinit var inspectionHighlightNonIndentedNestedIfsCheckbox: JCheckBox private lateinit var inspectionHighlightCommentsNotMatchingIfIndentsCheckbox: JCheckBox private lateinit var hideUnmatchedVersionsCheckbox: JCheckBox + private lateinit var addPreprocessorCommentOnEnterCheckbox: JCheckBox override fun getDisplayName(): String = "IntelliProcessor" @@ -53,6 +54,8 @@ class PluginConfigurable : Configurable { hideUnmatchedVersionsCheckbox = JCheckBox("Hide results that do not meet preprocessor conditions at the caret") .tooltip("Hides version results in the 'Jump To Pre-Processed File' dialog that do not match the current file's preprocessor conditions found at the caret position.") + addPreprocessorCommentOnEnterCheckbox = JCheckBox("Add preprocessor comment '//$$ ' automatically to new lines in a disabled preprocessor block") + .tooltip("When pressing Enter inside a disabled preprocessor block, automatically adds a preprocessor comment '//$$ ' to the new line.") // Arrange components @@ -72,7 +75,7 @@ class PluginConfigurable : Configurable { add(foldInactiveBlocksByDefaultCheckbox) }) - add(titledBlock("Inspection Highlighting") { + add(titledBlock("Formatting") { add(inspectionHighlightNonIndentedNestedIfsCheckbox) add(inspectionHighlightCommentsNotMatchingIfIndentsCheckbox) }) @@ -81,6 +84,10 @@ class PluginConfigurable : Configurable { add(hideUnmatchedVersionsCheckbox) }) + add(titledBlock("Misc") { + add(addPreprocessorCommentOnEnterCheckbox) + }) + add(titledBlock("Info") { add(JLabel("The keybinds can be configured from: Keymap > Plugins > IntelliProcessor")) }) @@ -96,6 +103,7 @@ class PluginConfigurable : Configurable { || inspectionHighlightNonIndentedNestedIfsCheckbox.isSelected != PluginSettings.instance.inspectionHighlightNonIndentedNestedIfs || inspectionHighlightCommentsNotMatchingIfIndentsCheckbox.isSelected != PluginSettings.instance.inspectionHighlightCommentsNotMatchingIfIndents || hideUnmatchedVersionsCheckbox.isSelected != PluginSettings.instance.hideUnmatchedVersions + || addPreprocessorCommentOnEnterCheckbox.isSelected != PluginSettings.instance.addPreprocessorCommentOnEnter override fun apply() { PluginSettings.instance.foldAllBlocksByDefault = foldAllBlocksByDefaultCheckbox.isSelected @@ -103,6 +111,7 @@ class PluginConfigurable : Configurable { PluginSettings.instance.inspectionHighlightNonIndentedNestedIfs = inspectionHighlightNonIndentedNestedIfsCheckbox.isSelected PluginSettings.instance.inspectionHighlightCommentsNotMatchingIfIndents = inspectionHighlightCommentsNotMatchingIfIndentsCheckbox.isSelected PluginSettings.instance.hideUnmatchedVersions = hideUnmatchedVersionsCheckbox.isSelected + PluginSettings.instance.addPreprocessorCommentOnEnter = addPreprocessorCommentOnEnterCheckbox.isSelected } override fun reset() { @@ -111,5 +120,6 @@ class PluginConfigurable : Configurable { inspectionHighlightNonIndentedNestedIfsCheckbox.isSelected = PluginSettings.instance.inspectionHighlightNonIndentedNestedIfs inspectionHighlightCommentsNotMatchingIfIndentsCheckbox.isSelected = PluginSettings.instance.inspectionHighlightCommentsNotMatchingIfIndents hideUnmatchedVersionsCheckbox.isSelected = PluginSettings.instance.hideUnmatchedVersions + addPreprocessorCommentOnEnterCheckbox.isSelected = PluginSettings.instance.addPreprocessorCommentOnEnter } } \ No newline at end of file diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/config/PluginSettings.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/config/PluginSettings.kt index 25f9bb1..63746a9 100644 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/config/PluginSettings.kt +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/config/PluginSettings.kt @@ -14,6 +14,7 @@ class PluginSettings : PersistentStateComponent { var inspectionHighlightNonIndentedNestedIfs: Boolean = true var inspectionHighlightCommentsNotMatchingIfIndents: Boolean = true var hideUnmatchedVersions: Boolean = false + var addPreprocessorCommentOnEnter = true override fun getState(): PluginSettings = this @@ -23,6 +24,7 @@ class PluginSettings : PersistentStateComponent { this.inspectionHighlightNonIndentedNestedIfs = state.inspectionHighlightNonIndentedNestedIfs this.inspectionHighlightCommentsNotMatchingIfIndents = state.inspectionHighlightCommentsNotMatchingIfIndents this.hideUnmatchedVersions = state.hideUnmatchedVersions + this.addPreprocessorCommentOnEnter = state.addPreprocessorCommentOnEnter } companion object { diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/editor/PreprocessorNewLineHandler.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/editor/PreprocessorNewLineHandler.kt index debe628..5bb6f1c 100644 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/editor/PreprocessorNewLineHandler.kt +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/editor/PreprocessorNewLineHandler.kt @@ -10,6 +10,7 @@ import com.intellij.openapi.project.DumbAware import com.intellij.openapi.util.Ref import com.intellij.psi.PsiFile import org.polyfrost.intelliprocessor.ALLOWED_FILE_TYPES +import org.polyfrost.intelliprocessor.config.PluginSettings import org.polyfrost.intelliprocessor.utils.* import org.polyfrost.intelliprocessor.utils.PreprocessorVersion.Companion.preprocessorVersion import java.util.Locale @@ -24,6 +25,11 @@ class PreprocessorNewLineHandler : EnterHandlerDelegateAdapter(), DumbAware { dataContext: DataContext, originalHandler: EditorActionHandler? ): Result { + + if (!PluginSettings.instance.addPreprocessorCommentOnEnter) { + return Result.Continue + } + val fileTypeName = EnterHandler.getLanguage(dataContext) ?.associatedFileType ?.name From 4eb2775426c7b6bf8e6b0ce37185f5866f3211db Mon Sep 17 00:00:00 2001 From: Traben Date: Fri, 3 Oct 2025 20:20:06 +1000 Subject: [PATCH 18/19] restore order="first" --- src/main/resources/META-INF/kotlin-plugin.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/META-INF/kotlin-plugin.xml b/src/main/resources/META-INF/kotlin-plugin.xml index 06acc68..2affa36 100644 --- a/src/main/resources/META-INF/kotlin-plugin.xml +++ b/src/main/resources/META-INF/kotlin-plugin.xml @@ -3,6 +3,7 @@ Date: Fri, 3 Oct 2025 20:21:33 +1000 Subject: [PATCH 19/19] minor formatting --- .../polyfrost/intelliprocessor/utils/SourceSetFileDialog.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/SourceSetFileDialog.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/SourceSetFileDialog.kt index bd4efa5..433e723 100644 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/SourceSetFileDialog.kt +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/SourceSetFileDialog.kt @@ -53,7 +53,9 @@ class SourceSetFileDialog( if (it.isNonGenerated) { font = font.deriveFont(Font.BOLD) } - if (!it.metOpeningCondition) foreground = JBColor.GRAY + if (!it.metOpeningCondition) { + foreground = JBColor.GRAY + } } } @@ -120,7 +122,7 @@ class SourceSetFileDialog( panel.add(search, BorderLayout.NORTH) panel.add(JBScrollPane(list), BorderLayout.CENTER) bottomPanelOrNull()?.let { - panel.add(it,BorderLayout.SOUTH) + panel.add(it, BorderLayout.SOUTH) } return panel }