From 075ef8f84f76c5a39499eef4aaea1d8d39abd28b Mon Sep 17 00:00:00 2001 From: Traben Date: Sat, 4 Oct 2025 19:52:40 +1000 Subject: [PATCH 1/5] select index 1 rather than 0 on down key press, also ensures that prior to this index 0 is selected by default --- .../intelliprocessor/utils/SourceSetFileDialog.kt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/SourceSetFileDialog.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/SourceSetFileDialog.kt index 433e723..dbce09a 100644 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/SourceSetFileDialog.kt +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/SourceSetFileDialog.kt @@ -92,8 +92,11 @@ class SourceSetFileDialog( 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 + if (list.selectedIndex == 0 && listModel.size > 1) { + // Set to 1 as the first index (0) should already be auto-selected meaning we can treat the down + // arrow like moving down as if we were in the list already, (which we kind of are since pressing + // enter will already open the selected item at 0 before this down press) + list.selectedIndex = 1 } list.requestFocusInWindow() e.consume() @@ -124,6 +127,7 @@ class SourceSetFileDialog( bottomPanelOrNull()?.let { panel.add(it, BorderLayout.SOUTH) } + filterList() return panel } @@ -154,7 +158,6 @@ class SourceSetFileDialog( filterList() } }) - filterList() } return if (belowList.isEmpty()) null else JPanel(GridLayout(belowList.size, 1)).apply { @@ -172,7 +175,7 @@ class SourceSetFileDialog( && (!PluginSettings.instance.hideUnmatchedVersions || it.metOpeningCondition) }) - if (filter.isEmpty() || listModel.isEmpty) { + if (listModel.isEmpty) { list.setSelectedValue(null, false) } else { // Improve keyboard navigation by auto-selecting the first result From 45cbcd49b7e1dce6f96cf2f25f25bb287e158b4d Mon Sep 17 00:00:00 2001 From: Traben Date: Sat, 4 Oct 2025 19:54:41 +1000 Subject: [PATCH 2/5] new action to toggle all preprocessor comments of a block --- .../action/PreprocessorCommentToggleAction.kt | 104 +++++++++++++++++ .../polyfrost/intelliprocessor/utils/utils.kt | 107 ++++++++++++++++++ src/main/resources/META-INF/plugin.xml | 8 +- 3 files changed, 218 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/org/polyfrost/intelliprocessor/action/PreprocessorCommentToggleAction.kt diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/action/PreprocessorCommentToggleAction.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/action/PreprocessorCommentToggleAction.kt new file mode 100644 index 0000000..5a17835 --- /dev/null +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/action/PreprocessorCommentToggleAction.kt @@ -0,0 +1,104 @@ +package org.polyfrost.intelliprocessor.action + +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.command.WriteCommandAction +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.TextRange +import org.jetbrains.kotlin.idea.base.psi.getLineNumber +import org.polyfrost.intelliprocessor.utils.PreprocessorContainingBlock +import org.polyfrost.intelliprocessor.utils.activeFile +import org.polyfrost.intelliprocessor.utils.allPreprocessorDirectiveComments + +class PreprocessorCommentToggleAction : AnAction() { + + override fun actionPerformed(e: AnActionEvent) { + val project: Project = e.project ?: return + val editor: Editor = FileEditorManager.getInstance(project).selectedTextEditor ?: return warning(project, "Could not find an open editor") + val document = editor.document + val file = editor.activeFile ?: return warning(project, "Could not find an opened file") + + val directives = file.allPreprocessorDirectiveComments() + + if (directives.size < 2) return warning(project, "Could not find any preprocessor blocks in this file") + + val block = PreprocessorContainingBlock.getFor(editor.caretModel.offset, directives) + ?: return warning(project, "Could not find a preprocessor block at the current caret position") + + + val startLine = block.directives[block.startIndex].getLineNumber(true) + 1 + val endLine = block.directives[block.endIndex].getLineNumber(true) - 1 + + if (startLine > endLine) return warning(project, "No lines to toggle between the selected directives") + + val excludeLines = block.innerBlocks.map { + IntRange( + block.directives[it.start].getLineNumber(true), + block.directives[it.endInclusive].getLineNumber(true) + ) + } + + val startIndent = countLeadingSpaces( + document.getText(TextRange( + document.getLineStartOffset(startLine - 1), + document.getLineEndOffset(startLine - 1) + )) + ) + + val alreadyHasComments = document.getText(TextRange( + document.getLineStartOffset(startLine), + document.getLineEndOffset(startLine) + )).trim().startsWith("//$$") + + val spaces = " ".repeat(startIndent) + val spacedComment = "$spaces//$$ " + + WriteCommandAction.runWriteCommandAction(project) { + for (line in startLine..endLine) { + + // Don't modify lines that are part of inner nested blocks + if (excludeLines.any { line in it }) continue + + val lineStart = document.getLineStartOffset(line) + val lineEnd = document.getLineEndOffset(line) + val text = document.getText(TextRange(lineStart, lineEnd)) + + // Just remove comments if they are already there + if (alreadyHasComments) { + document.replaceString(lineStart, lineEnd, + text.replaceFirst(REPLACE, "")) + continue + } + + // If the line is blank, just insert a blank comment + if (text.trim().isEmpty()) { + document.replaceString(lineStart, lineEnd, spacedComment) + continue + } + + // Only comment lines that are indented at least as much as the block start + val indent = countLeadingSpaces(text) + if (indent >= startIndent) { + document.replaceString(lineStart, lineEnd, + text.replaceFirst(spaces, spacedComment)) + } + } + } + } + + private fun countLeadingSpaces(s: String): Int { + var count = 0 + while (count < s.length && s[count] == ' ') count++ + return count + } + + companion object { + private val REPLACE = Regex("""//\$\$\s?""") + + private fun warning(project: Project, content: String) { + org.polyfrost.intelliprocessor.utils.warning(project, "preprocessor_comment_toggle_failure", content) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/utils.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/utils.kt index 3ab6de3..0ddd70e 100644 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/utils/utils.kt +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/utils/utils.kt @@ -51,4 +51,111 @@ fun PsiFile.allPreprocessorDirectiveComments(): List { val directivePrefix = directivePrefix() ?: return emptyList() return PsiTreeUtil.findChildrenOfType(this, PsiComment::class.java) .filter { it.text.startsWith(directivePrefix) } +} + +data class PreprocessorContainingBlock( + val directives: List, + val startIndex: Int, + val endIndex: Int, + val innerBlocks: List +) { + companion object { + fun getFor(offset: Int, directives: List): PreprocessorContainingBlock? { + + // First lets check this caret pos is actually between some directives before we get any more complex + val previous = directives.lastOrNull { it.textRange.endOffset <= offset } ?: return null + val next = directives.firstOrNull { it.textRange.startOffset > offset } ?: return null + + // Okay now lets find the containing block, this is a bit tricky because of possible nesting + // We will iterate backwards from the previous directive to find the start of the block + // and forwards from the next directive to find the end of the block + // We also need to keep track of any inner blocks we find along the way so they can be safely ignored later + + val innerBlocks = mutableListOf() + + // Iterate backwards to find the previous containing directive + val startIndex: Int + if (previous.text.startsWith("//#endif")) { + // Nested block, we need to find the matching block start for our level + var depth = 1 + var index = directives.indexOf(previous) + var lastEnd = index + var find: Int? = null + while (index > 0) { + index-- + val text = directives[index].text + if (text.startsWith("//#endif")) { + depth++ + if (depth == 1) { + lastEnd = index + } + } else if (text.startsWith("//#if") || text.startsWith("//#ifdef")) { + if (depth == 0) { + find = index + break + } + if (depth == 1) { + innerBlocks.add(index..lastEnd) + } + depth-- + } else if (text.startsWith("//#else") || text.startsWith("//#elseif")) { + if (depth == 0) { + find = index + break + } + } + } + startIndex = find ?: return null + } else { + // Simple case, just the previous directive of the same level + startIndex = directives.indexOf(previous) + } + + + // Now find the next directive to determine the end of the block, this basically repeats the above logic + val endIndex: Int + if (next.text.startsWith("//#if") || next.text.startsWith("//#ifdef")) { + // Nested block, we need to find the matching block end for our level + var depth = 1 + var index = directives.indexOf(next) + var lastStart = index + var find: Int? = null + while (index < directives.size - 1) { + index++ + val text = directives[index].text + if (text.startsWith("//#if") || text.startsWith("//#ifdef")) { + depth++ + if (depth == 1) { + lastStart = index + } + } else if (text.startsWith("//#endif")) { + if (depth == 0) { + find = index + break + } + if (depth == 1) { + innerBlocks.add(lastStart..index) + } + depth-- + } else if (text.startsWith("//#else") || text.startsWith("//#elseif")) { + if (depth == 0) { + find = index + break + } + } + } + endIndex = find ?: return null + } else { + // Simple case, just the next directive of the same level + endIndex = directives.indexOf(next) + } + + return PreprocessorContainingBlock( + directives, + startIndex, + endIndex, + innerBlocks + ) + } + } } \ 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 9f42a73..784651e 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -31,5 +31,11 @@ description="Jump from or to this file in the preprocessed source. Will not update those source files, so you might need to build your project to update those files."> - + + + + From 4bf020673e8b05fdf9490296a74db8c2bf20c1fb Mon Sep 17 00:00:00 2001 From: Traben Date: Mon, 6 Oct 2025 18:45:18 +1000 Subject: [PATCH 3/5] improve block action and add line select action --- ...> PreprocessorCommentToggleBlockAction.kt} | 17 ++--- .../PreprocessorCommentToggleLineAction.kt | 66 +++++++++++++++++++ src/main/resources/META-INF/plugin.xml | 10 ++- 3 files changed, 83 insertions(+), 10 deletions(-) rename src/main/kotlin/org/polyfrost/intelliprocessor/action/{PreprocessorCommentToggleAction.kt => PreprocessorCommentToggleBlockAction.kt} (90%) create mode 100644 src/main/kotlin/org/polyfrost/intelliprocessor/action/PreprocessorCommentToggleLineAction.kt diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/action/PreprocessorCommentToggleAction.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/action/PreprocessorCommentToggleBlockAction.kt similarity index 90% rename from src/main/kotlin/org/polyfrost/intelliprocessor/action/PreprocessorCommentToggleAction.kt rename to src/main/kotlin/org/polyfrost/intelliprocessor/action/PreprocessorCommentToggleBlockAction.kt index 5a17835..f689a30 100644 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/action/PreprocessorCommentToggleAction.kt +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/action/PreprocessorCommentToggleBlockAction.kt @@ -12,7 +12,7 @@ import org.polyfrost.intelliprocessor.utils.PreprocessorContainingBlock import org.polyfrost.intelliprocessor.utils.activeFile import org.polyfrost.intelliprocessor.utils.allPreprocessorDirectiveComments -class PreprocessorCommentToggleAction : AnAction() { +class PreprocessorCommentToggleBlockAction : AnAction() { override fun actionPerformed(e: AnActionEvent) { val project: Project = e.project ?: return @@ -47,7 +47,7 @@ class PreprocessorCommentToggleAction : AnAction() { )) ) - val alreadyHasComments = document.getText(TextRange( + val toggleCommentsOff = document.getText(TextRange( document.getLineStartOffset(startLine), document.getLineEndOffset(startLine) )).trim().startsWith("//$$") @@ -65,12 +65,10 @@ class PreprocessorCommentToggleAction : AnAction() { val lineEnd = document.getLineEndOffset(line) val text = document.getText(TextRange(lineStart, lineEnd)) - // Just remove comments if they are already there - if (alreadyHasComments) { - document.replaceString(lineStart, lineEnd, - text.replaceFirst(REPLACE, "")) - continue - } + // Clean up any existing toggle comments + document.replaceString(lineStart, lineEnd, text.replaceFirst(REPLACE, "")) + + if (toggleCommentsOff) continue // If the line is blank, just insert a blank comment if (text.trim().isEmpty()) { @@ -83,6 +81,9 @@ class PreprocessorCommentToggleAction : AnAction() { if (indent >= startIndent) { document.replaceString(lineStart, lineEnd, text.replaceFirst(spaces, spacedComment)) + } else { + document.replaceString(lineStart, lineEnd, + spacedComment + text.trimStart()) } } } diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/action/PreprocessorCommentToggleLineAction.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/action/PreprocessorCommentToggleLineAction.kt new file mode 100644 index 0000000..214683b --- /dev/null +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/action/PreprocessorCommentToggleLineAction.kt @@ -0,0 +1,66 @@ +package org.polyfrost.intelliprocessor.action + +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.command.WriteCommandAction +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.TextRange +import org.jetbrains.kotlin.idea.base.psi.getLineNumber +import org.polyfrost.intelliprocessor.utils.PreprocessorContainingBlock +import org.polyfrost.intelliprocessor.utils.activeFile +import org.polyfrost.intelliprocessor.utils.allPreprocessorDirectiveComments + +class PreprocessorCommentToggleLineAction : AnAction() { + + override fun actionPerformed(e: AnActionEvent) { + val project: Project = e.project ?: return + val editor: Editor = FileEditorManager.getInstance(project).selectedTextEditor ?: return warning(project, "Could not find an open editor") + val document = editor.document + + val startLine = editor.caretModel.primaryCaret.selectionStartPosition.line + val endLine = editor.caretModel.primaryCaret.selectionEndPosition.line + + WriteCommandAction.runWriteCommandAction(project) { + for (line in startLine..endLine) { + val lineStart = document.getLineStartOffset(line) + val lineEnd = document.getLineEndOffset(line) + val text = document.getText(TextRange(lineStart, lineEnd)) + + val toggleCommentsOff = text.contains("//$$") + + // Clean up any existing toggle comments + document.replaceString(lineStart, lineEnd, text.replaceFirst(REPLACE, "")) + + if (toggleCommentsOff) return@runWriteCommandAction + + // If the line is blank, just insert a blank comment + if (text.trim().isEmpty()) { + document.replaceString(lineStart, lineEnd, "//$$ ") + return@runWriteCommandAction + } + + // Only comment lines that are indented at least as much as the block start + val indent = countLeadingSpaces(text) + val spaces = " ".repeat(indent) + val spacedComment = "$spaces//$$ " + document.replaceString(lineStart, lineEnd, text.replaceFirst(spaces, spacedComment)) + } + } + } + + private fun countLeadingSpaces(s: String): Int { + var count = 0 + while (count < s.length && s[count] == ' ') count++ + return count + } + + companion object { + private val REPLACE = Regex("""//\$\$\s?""") + + private fun warning(project: Project, content: String) { + org.polyfrost.intelliprocessor.utils.warning(project, "preprocessor_comment_toggle_failure", content) + } + } +} \ 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 784651e..70dcfd2 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -31,11 +31,17 @@ description="Jump from or to this file in the preprocessed source. Will not update those source files, so you might need to build your project to update those files."> - + + + From 0262eff7571b6a5dde8fab986e09f471aa0b6b4d Mon Sep 17 00:00:00 2001 From: Traben Date: Mon, 6 Oct 2025 18:48:43 +1000 Subject: [PATCH 4/5] fix `!IDENTIFIER` conditions being highlighted where `IDENTIFIER` isn't --- .../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 6722047..82b8c04 100644 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/editor/PreprocessorSyntaxHighlight.kt +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/editor/PreprocessorSyntaxHighlight.kt @@ -55,7 +55,7 @@ val NUMBER_TYPE = HighlightInfoType.HighlightInfoTypeImpl(HighlightSeverity.INFO private val WHITESPACES_PATTERN = "\\s+".toRegex() private val EXPR_PATTERN = "(.+)(==|!=|<=|>=|<|>)(.+)".toRegex() -private val IDENTIFIER_PATTERN = "[A-Za-z0-9-]+".toRegex() +private val IDENTIFIER_PATTERN = "!?[A-Za-z0-9-]+".toRegex() private val OR_PATTERN = Regex.escape("||") private val AND_PATTERN = Regex.escape("&&") private val SPLIT_PATTERN = "$OR_PATTERN|$AND_PATTERN".toRegex() From c7e13e16c40dae5edf465e79e7f7110791390f5d Mon Sep 17 00:00:00 2001 From: Traben Date: Mon, 6 Oct 2025 19:37:42 +1000 Subject: [PATCH 5/5] fix toggle actions --- .../PreprocessorCommentToggleBlockAction.kt | 14 ++++++++-- .../PreprocessorCommentToggleLineAction.kt | 28 +++++++++---------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/action/PreprocessorCommentToggleBlockAction.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/action/PreprocessorCommentToggleBlockAction.kt index f689a30..3473c45 100644 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/action/PreprocessorCommentToggleBlockAction.kt +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/action/PreprocessorCommentToggleBlockAction.kt @@ -63,12 +63,20 @@ class PreprocessorCommentToggleBlockAction : AnAction() { val lineStart = document.getLineStartOffset(line) val lineEnd = document.getLineEndOffset(line) - val text = document.getText(TextRange(lineStart, lineEnd)) + var text = document.getText(TextRange(lineStart, lineEnd)) + + if (text.trimStart().startsWith("//#")) { + warning(project, "Action tried to modify preprocessor directive line at line#${line + 1}, aborting.") + break // something is very wrong abort + } // Clean up any existing toggle comments - document.replaceString(lineStart, lineEnd, text.replaceFirst(REPLACE, "")) + text = text.replaceFirst(REPLACE, "") - if (toggleCommentsOff) continue + if (toggleCommentsOff) { + document.replaceString(lineStart, lineEnd, text) + continue + } // If the line is blank, just insert a blank comment if (text.trim().isEmpty()) { diff --git a/src/main/kotlin/org/polyfrost/intelliprocessor/action/PreprocessorCommentToggleLineAction.kt b/src/main/kotlin/org/polyfrost/intelliprocessor/action/PreprocessorCommentToggleLineAction.kt index 214683b..93039e0 100644 --- a/src/main/kotlin/org/polyfrost/intelliprocessor/action/PreprocessorCommentToggleLineAction.kt +++ b/src/main/kotlin/org/polyfrost/intelliprocessor/action/PreprocessorCommentToggleLineAction.kt @@ -7,10 +7,6 @@ import com.intellij.openapi.editor.Editor import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.project.Project import com.intellij.openapi.util.TextRange -import org.jetbrains.kotlin.idea.base.psi.getLineNumber -import org.polyfrost.intelliprocessor.utils.PreprocessorContainingBlock -import org.polyfrost.intelliprocessor.utils.activeFile -import org.polyfrost.intelliprocessor.utils.allPreprocessorDirectiveComments class PreprocessorCommentToggleLineAction : AnAction() { @@ -19,26 +15,28 @@ class PreprocessorCommentToggleLineAction : AnAction() { val editor: Editor = FileEditorManager.getInstance(project).selectedTextEditor ?: return warning(project, "Could not find an open editor") val document = editor.document - val startLine = editor.caretModel.primaryCaret.selectionStartPosition.line - val endLine = editor.caretModel.primaryCaret.selectionEndPosition.line + val startLine = document.getLineNumber(editor.selectionModel.selectionStart) + val endLine = document.getLineNumber(editor.selectionModel.selectionEnd) WriteCommandAction.runWriteCommandAction(project) { for (line in startLine..endLine) { val lineStart = document.getLineStartOffset(line) val lineEnd = document.getLineEndOffset(line) val text = document.getText(TextRange(lineStart, lineEnd)) - - val toggleCommentsOff = text.contains("//$$") - - // Clean up any existing toggle comments - document.replaceString(lineStart, lineEnd, text.replaceFirst(REPLACE, "")) - - if (toggleCommentsOff) return@runWriteCommandAction + val trim = text.trim() // If the line is blank, just insert a blank comment - if (text.trim().isEmpty()) { + if (trim.isEmpty()) { document.replaceString(lineStart, lineEnd, "//$$ ") - return@runWriteCommandAction + continue + } + + if (trim.startsWith("//#")) continue + + if (trim.startsWith("//$$")) { + // Clean up any existing toggle comments + document.replaceString(lineStart, lineEnd, text.replaceFirst(REPLACE, "")) + continue } // Only comment lines that are indented at least as much as the block start