diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/PreprocessorImport.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/PreprocessorImport.kt index 705b080..3f378ea 100644 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/PreprocessorImport.kt +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/PreprocessorImport.kt @@ -22,7 +22,7 @@ class PreprocessorImport : ImportOptimizer { if (!hasPreprocessorDirectives(imports)) { return LanguageImportStatements.INSTANCE .allForLanguage(file.language) - .first { it !is JavaImportOptimizer } + .first { it is JavaImportOptimizer } .processFile(file) } diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/config/PluginConfigurable.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/config/PluginConfigurable.kt index bd6c1fc..51031a9 100644 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/config/PluginConfigurable.kt +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/config/PluginConfigurable.kt @@ -17,7 +17,10 @@ class PluginConfigurable : Configurable { private lateinit var inspectionHighlightCommentsNotMatchingIfIndentsCheckbox: JCheckBox private lateinit var hideUnmatchedVersionsCheckbox: JCheckBox private lateinit var addPreprocessorCommentOnEnterCheckbox: JCheckBox - + private lateinit var colorNestedPreprocessorCommentsCheckbox: JCheckBox + private lateinit var colorNestedPreprocessorCommentsOnlyOnSameIndentCheckbox: JCheckBox + private lateinit var inspectionHighlightContentNotMatchingIfIndentsCheckbox: JCheckBox + private lateinit var inspectionRequireJavaKotlinSpacingInConditionsCheckbox: JCheckBox override fun getDisplayName(): String = "IntelliProcessor" @@ -57,6 +60,19 @@ class PluginConfigurable : Configurable { 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.") + colorNestedPreprocessorCommentsCheckbox = JCheckBox("Color nested preprocessor comments differently") + .tooltip("Colors preprocessor comments inside nested preprocessor blocks with a different color for better visibility.") + + colorNestedPreprocessorCommentsOnlyOnSameIndentCheckbox = JCheckBox("Only recolor nested preprocessor comments when in-line with each other") + .tooltip("Only applies the different color to nested preprocessor comments when their indents align.") + + inspectionHighlightContentNotMatchingIfIndentsCheckbox = JCheckBox("Highlight lines indented less than their \"if\"'s indent") + .tooltip("Highlights lines of code inside preprocessor blocks that are indented less than the corresponding \"if\" directive.\n" + + "This does break preprocessing.") + + inspectionRequireJavaKotlinSpacingInConditionsCheckbox = JCheckBox("Highlight non-standard Java / Kotlin spacing around operators in preprocessor conditions") + .tooltip("Highlights improper use of spaces around operators in preprocessor conditions for improved readability.") + // Arrange components fun titledBlock(str: String, block: JPanel.() -> Unit): JPanel = JPanel().apply { @@ -78,6 +94,10 @@ class PluginConfigurable : Configurable { add(titledBlock("Formatting") { add(inspectionHighlightNonIndentedNestedIfsCheckbox) add(inspectionHighlightCommentsNotMatchingIfIndentsCheckbox) + add(inspectionHighlightContentNotMatchingIfIndentsCheckbox) + add(colorNestedPreprocessorCommentsCheckbox) + add(colorNestedPreprocessorCommentsOnlyOnSameIndentCheckbox) + add(inspectionRequireJavaKotlinSpacingInConditionsCheckbox) }) add(titledBlock("Jump To Pre-Processed File Action") { @@ -104,6 +124,10 @@ class PluginConfigurable : Configurable { || inspectionHighlightCommentsNotMatchingIfIndentsCheckbox.isSelected != PluginSettings.instance.inspectionHighlightCommentsNotMatchingIfIndents || hideUnmatchedVersionsCheckbox.isSelected != PluginSettings.instance.hideUnmatchedVersions || addPreprocessorCommentOnEnterCheckbox.isSelected != PluginSettings.instance.addPreprocessorCommentOnEnter + || colorNestedPreprocessorCommentsCheckbox.isSelected != PluginSettings.instance.colorNestedPreprocessorComments + || colorNestedPreprocessorCommentsOnlyOnSameIndentCheckbox.isSelected != PluginSettings.instance.colorNestedPreprocessorCommentsOnlyOnSameIndent + || inspectionHighlightContentNotMatchingIfIndentsCheckbox.isSelected != PluginSettings.instance.inspectionHighlightContentNotMatchingIfIndents + || inspectionRequireJavaKotlinSpacingInConditionsCheckbox.isSelected != PluginSettings.instance.inspectionRequireJavaKotlinSpacingInConditions override fun apply() { PluginSettings.instance.foldAllBlocksByDefault = foldAllBlocksByDefaultCheckbox.isSelected @@ -112,6 +136,10 @@ class PluginConfigurable : Configurable { PluginSettings.instance.inspectionHighlightCommentsNotMatchingIfIndents = inspectionHighlightCommentsNotMatchingIfIndentsCheckbox.isSelected PluginSettings.instance.hideUnmatchedVersions = hideUnmatchedVersionsCheckbox.isSelected PluginSettings.instance.addPreprocessorCommentOnEnter = addPreprocessorCommentOnEnterCheckbox.isSelected + PluginSettings.instance.colorNestedPreprocessorComments = colorNestedPreprocessorCommentsCheckbox.isSelected + PluginSettings.instance.colorNestedPreprocessorCommentsOnlyOnSameIndent = colorNestedPreprocessorCommentsOnlyOnSameIndentCheckbox.isSelected + PluginSettings.instance.inspectionHighlightContentNotMatchingIfIndents = inspectionHighlightContentNotMatchingIfIndentsCheckbox.isSelected + PluginSettings.instance.inspectionRequireJavaKotlinSpacingInConditions = inspectionRequireJavaKotlinSpacingInConditionsCheckbox.isSelected } override fun reset() { @@ -121,5 +149,9 @@ class PluginConfigurable : Configurable { inspectionHighlightCommentsNotMatchingIfIndentsCheckbox.isSelected = PluginSettings.instance.inspectionHighlightCommentsNotMatchingIfIndents hideUnmatchedVersionsCheckbox.isSelected = PluginSettings.instance.hideUnmatchedVersions addPreprocessorCommentOnEnterCheckbox.isSelected = PluginSettings.instance.addPreprocessorCommentOnEnter + colorNestedPreprocessorCommentsCheckbox.isSelected = PluginSettings.instance.colorNestedPreprocessorComments + colorNestedPreprocessorCommentsOnlyOnSameIndentCheckbox.isSelected = PluginSettings.instance.colorNestedPreprocessorCommentsOnlyOnSameIndent + inspectionHighlightContentNotMatchingIfIndentsCheckbox.isSelected = PluginSettings.instance.inspectionHighlightContentNotMatchingIfIndents + inspectionRequireJavaKotlinSpacingInConditionsCheckbox.isSelected = PluginSettings.instance.inspectionRequireJavaKotlinSpacingInConditions } } \ 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 63746a9..eb7b311 100644 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/config/PluginSettings.kt +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/config/PluginSettings.kt @@ -11,10 +11,14 @@ import com.intellij.openapi.components.service class PluginSettings : PersistentStateComponent { var foldAllBlocksByDefault: Boolean = false var foldInactiveBlocksByDefault: Boolean = true - var inspectionHighlightNonIndentedNestedIfs: Boolean = true + var inspectionHighlightNonIndentedNestedIfs: Boolean = false var inspectionHighlightCommentsNotMatchingIfIndents: Boolean = true var hideUnmatchedVersions: Boolean = false var addPreprocessorCommentOnEnter = true + var colorNestedPreprocessorComments = true + var colorNestedPreprocessorCommentsOnlyOnSameIndent = false + var inspectionHighlightContentNotMatchingIfIndents = true + var inspectionRequireJavaKotlinSpacingInConditions = false override fun getState(): PluginSettings = this @@ -25,6 +29,10 @@ class PluginSettings : PersistentStateComponent { this.inspectionHighlightCommentsNotMatchingIfIndents = state.inspectionHighlightCommentsNotMatchingIfIndents this.hideUnmatchedVersions = state.hideUnmatchedVersions this.addPreprocessorCommentOnEnter = state.addPreprocessorCommentOnEnter + this.colorNestedPreprocessorComments = state.colorNestedPreprocessorComments + this.colorNestedPreprocessorCommentsOnlyOnSameIndent = state.colorNestedPreprocessorCommentsOnlyOnSameIndent + this.inspectionHighlightContentNotMatchingIfIndents = state.inspectionHighlightContentNotMatchingIfIndents + this.inspectionRequireJavaKotlinSpacingInConditions = state.inspectionRequireJavaKotlinSpacingInConditions } companion object { diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/editor/PreprocessorSyntaxHighlight.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/editor/PreprocessorSyntaxHighlight.kt index 82b8c04..6c46543 100644 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/editor/PreprocessorSyntaxHighlight.kt +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/editor/PreprocessorSyntaxHighlight.kt @@ -19,11 +19,13 @@ import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.PsiRecursiveElementWalkingVisitor import com.intellij.psi.impl.source.tree.PsiCommentImpl +import com.intellij.ui.JBColor 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 +import org.polyfrost.intelliprocessor.utils.PreprocessorVersion.Companion.toPreprocessorIntOrNull +import java.awt.Color +import java.util.* val SCHEME = EditorColorsManager.getInstance().globalScheme @@ -35,16 +37,23 @@ fun TextAttributes.fade(): TextAttributes { val r = c.red + (COMMENT_COLOR.red - c.red) / 2 val g = c.green + (COMMENT_COLOR.green - c.green) / 2 val b = c.blue + (COMMENT_COLOR.blue - c.blue) / 2 - java.awt.Color(r, g, b) + Color(r, g, b) } return this } +fun fadedItalic(color: Color? = null): TextAttributes { + val attributes = TextAttributes.merge(SCHEME.getAttributes(DIRECTIVE_COLOR), ITALIC_ATTRIBUTE) + if (color != null) attributes.foregroundColor = color + return attributes.fade() +} + val BOLD_ATTRIBUTE = TextAttributes(null, null, null, null, java.awt.Font.BOLD) val ITALIC_ATTRIBUTE = TextAttributes(null, null, null, null, java.awt.Font.ITALIC) val DIRECTIVE_COLOR: TextAttributesKey = DefaultLanguageHighlighterColors.KEYWORD -val DIRECTIVE_ATTRIBUTES: TextAttributes = TextAttributes.merge(SCHEME.getAttributes(DIRECTIVE_COLOR), ITALIC_ATTRIBUTE).fade() +val DIRECTIVE_ATTRIBUTES_NESTED = listOf(null, JBColor.YELLOW, JBColor.GREEN, JBColor.CYAN, JBColor.BLUE, JBColor.MAGENTA) + .map { fadedItalic(it) } val DIRECTIVE_TYPE = HighlightInfoType.HighlightInfoTypeImpl(HighlightSeverity.INFORMATION, DIRECTIVE_COLOR) val IDENTIFIER_COLOR: TextAttributesKey = DefaultLanguageHighlighterColors.IDENTIFIER val IDENTIFIER_ATTRIBUTES: TextAttributes = TextAttributes.merge(SCHEME.getAttributes(IDENTIFIER_COLOR), BOLD_ATTRIBUTE).fade() @@ -71,6 +80,9 @@ class PreprocessorSyntaxHighlight(private val project: Project) : HighlightVisit private val doNestedIfIdentWarn get() = PluginSettings.instance.inspectionHighlightNonIndentedNestedIfs private val doIdentMatchWarn get() = PluginSettings.instance.inspectionHighlightCommentsNotMatchingIfIndents + private val colorNestedIndents get() = PluginSettings.instance.colorNestedPreprocessorComments + private val colorNestedIndentsOnlyIfInLine get() = PluginSettings.instance.colorNestedPreprocessorCommentsOnlyOnSameIndent + private val doSpacingWarn get() = PluginSettings.instance.inspectionRequireJavaKotlinSpacingInConditions override fun suitableForFile(file: PsiFile): Boolean { return file.fileType.name.uppercase(Locale.ROOT) in ALLOWED_FILE_TYPES @@ -107,8 +119,18 @@ class PreprocessorSyntaxHighlight(private val project: Project) : HighlightVisit return true } + fun nonPreprocessorElement(element: PsiElement) { + if (indentStack.isNotEmpty()) { + val indent = indentGetter(element) + if (indentStack.peekFirst() > indent) { + fail(element, "Content of preprocessor block is not indented the same or more than the containing preprocessor comment, this may not get processed correctly!") + } + } + } + override fun visit(element: PsiElement) { if (element !is PsiCommentImpl) { + nonPreprocessorElement(element) return } @@ -159,18 +181,16 @@ 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)") - } + 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)") } } @@ -191,6 +211,8 @@ class PreprocessorSyntaxHighlight(private val project: Project) : HighlightVisit EXPR_PATTERN.matchEntire(trimmed)?.let { m -> m.groups[1]?.toHighlight(element, position)?.let(holder::add) + if (doSpacingWarn && !trimmed.contains(" ${m.groups[2]?.value} ")) + warn(element, "Operator \"${m.groups[2]?.value}\" should be surrounded by spaces for clarity & consistency with Java / Kotlin styling.") m.groups[3]?.toHighlight(element, position)?.let(holder::add) } ?: run { IDENTIFIER_PATTERN.matchEntire(trimmed)?.let { idMatch -> @@ -201,14 +223,15 @@ class PreprocessorSyntaxHighlight(private val project: Project) : HighlightVisit } private fun handleIfDef(element: PsiCommentImpl, segments: List, prefixLength: Int) { + + val indent = indentGetter(element) 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) } + indentStack.push(indent) stack.push(Scope.IF) holder.add("ifdef".toDirectiveHighlight(element, prefixLength)) @@ -263,11 +286,11 @@ class PreprocessorSyntaxHighlight(private val project: Project) : HighlightVisit 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)) + indentStack.pop() if (segments.size > 1) { fail(element, "\"endif\" should not have arguments") @@ -306,6 +329,16 @@ class PreprocessorSyntaxHighlight(private val project: Project) : HighlightVisit highlightType(element, message, eol, HighlightInfoType.WEAK_WARNING) private fun highlightType(element: PsiElement, message: String, eol: Boolean = false, type: HighlightInfoType) { + for (i in 0..holder.size() - 1) { + val info = holder.get(i) + if (info.startOffset == element.textRange.startOffset + && info.description == message + && info.type == type + ) { + return // Avoid repeating the same highlight for each element + } + } + val builder = HighlightInfo.newHighlightInfo(type) .descriptionAndTooltip(message) @@ -320,8 +353,8 @@ class PreprocessorSyntaxHighlight(private val project: Project) : HighlightVisit private fun MatchGroup.toHighlight(element: PsiCommentImpl, offset: Int): HighlightInfo? { val trimmed = value.trim() - val type = if (trimmed.toIntOrNull() != null) NUMBER_TYPE else IDENTIFIER_TYPE - val attr = if (trimmed.toIntOrNull() != null) NUMBER_ATTRIBUTES else IDENTIFIER_ATTRIBUTES + val type = if (trimmed.toPreprocessorIntOrNull() != null) NUMBER_TYPE else IDENTIFIER_TYPE + val attr = if (trimmed.toPreprocessorIntOrNull() != null) NUMBER_ATTRIBUTES else IDENTIFIER_ATTRIBUTES return HighlightInfo.newHighlightInfo(type) .textAttributes(attr) .range( @@ -333,7 +366,16 @@ class PreprocessorSyntaxHighlight(private val project: Project) : HighlightVisit private fun String.toDirectiveHighlight(element: PsiCommentImpl, offset: Int): HighlightInfo? = HighlightInfo.newHighlightInfo(DIRECTIVE_TYPE) - .textAttributes(DIRECTIVE_ATTRIBUTES) + .textAttributes(DIRECTIVE_ATTRIBUTES_NESTED.let { + if (colorNestedIndents) { + val amount = if (colorNestedIndentsOnlyIfInLine) { + indentStack.count { i -> i == indentGetter(element)} - 1 + } else { + indentStack.size - 1 + }.coerceAtLeast(0) + it[amount % it.size] + } else it[0] + }) .range(element.startOffset + offset, element.startOffset + offset + 1 + length) .create() diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/PreprocessorConditions.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/PreprocessorConditions.kt index 4ef7a58..e065968 100644 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/PreprocessorConditions.kt +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/PreprocessorConditions.kt @@ -3,6 +3,7 @@ package org.polyfrost.intelliprocessor.utils import com.intellij.psi.PsiComment import com.intellij.psi.PsiFile import com.intellij.psi.util.startOffset +import org.polyfrost.intelliprocessor.utils.PreprocessorVersion.Companion.toPreprocessorIntOrNull class PreprocessorConditions private constructor( @@ -176,7 +177,7 @@ class PreprocessorConditions private constructor( val (operator, rhsStr) = match.destructured - val rhs = rhsStr.toIntOrNull() + val rhs = rhsStr.toPreprocessorIntOrNull() ?: return logAndNull("Could not evaluate version number in MC condition: $condition") val compare = currentVersion.mc diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/PreprocessorVersion.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/PreprocessorVersion.kt index bdcbced..4035d7f 100644 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/PreprocessorVersion.kt +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/PreprocessorVersion.kt @@ -15,7 +15,7 @@ class PreprocessorVersion private constructor(val mc: Int, val loader: String) { val NULL = PreprocessorVersion(0, "null") val String.preprocessorVersion: PreprocessorVersion? get() { - val int = makeComparable(this) ?: return null + val int = sugarToInt(this) ?: return null val loader = split("-").getOrNull(1) ?: return null return PreprocessorVersion(int, loader) } @@ -53,8 +53,17 @@ class PreprocessorVersion private constructor(val mc: Int, val loader: String) { return Files.readString(versionFile).trim() } + /** + * Converts a preprocessor version string to an integer. + * Supports both sugar version format (e.g., `1.16.5`) and integer format (e.g., `11605`). + */ + fun String.toPreprocessorIntOrNull() : Int? = + if (this.contains(".")) sugarToInt(this) + else this.toIntOrNull() + + private val regex = "(?\\d+)\\.(?\\d+)(?:\\.(?\\d+))?".toRegex() - private fun makeComparable(version: String): Int? { + private fun sugarToInt(version: String): Int? { val match = regex.find(version) ?: return null val groups = match.groups