Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
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 PreprocessorCommentToggleBlockAction : 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 toggleCommentsOff = 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)
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
text = text.replaceFirst(REPLACE, "")

if (toggleCommentsOff) {
document.replaceString(lineStart, lineEnd, text)
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))
} else {
document.replaceString(lineStart, lineEnd,
spacedComment + text.trimStart())
}
}
}
}

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)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
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

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 = 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 trim = text.trim()

// If the line is blank, just insert a blank comment
if (trim.isEmpty()) {
document.replaceString(lineStart, lineEnd, "//$$ ")
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
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)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -124,6 +127,7 @@ class SourceSetFileDialog(
bottomPanelOrNull()?.let {
panel.add(it, BorderLayout.SOUTH)
}
filterList()
return panel
}

Expand Down Expand Up @@ -154,7 +158,6 @@ class SourceSetFileDialog(
filterList()
}
})
filterList()
}

return if (belowList.isEmpty()) null else JPanel(GridLayout(belowList.size, 1)).apply {
Expand All @@ -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
Expand Down
107 changes: 107 additions & 0 deletions src/main/kotlin/org/polyfrost/intelliprocessor/utils/utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,111 @@ fun PsiFile.allPreprocessorDirectiveComments(): List<PsiComment> {
val directivePrefix = directivePrefix() ?: return emptyList()
return PsiTreeUtil.findChildrenOfType(this, PsiComment::class.java)
.filter { it.text.startsWith(directivePrefix) }
}

data class PreprocessorContainingBlock(
val directives: List<PsiComment>,
val startIndex: Int,
val endIndex: Int,
val innerBlocks: List<IntRange>
) {
companion object {
fun getFor(offset: Int, directives: List<PsiComment>): 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<IntRange>()

// 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
)
}
}
}
14 changes: 13 additions & 1 deletion src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +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.">
<add-to-group group-id="ToolsMenu" anchor="last"/>
</action>
</actions>
<action id="org.polyfrost.intelliprocessor.action.PreprocessorCommentToggleBlockAction"
class="org.polyfrost.intelliprocessor.action.PreprocessorCommentToggleBlockAction"
text="Toggle Preprocessor Comments Of Block &quot;//$$ &quot;"
description="Toggles all preprocessor comments (//$$ ) in the selected block">
<add-to-group group-id="ToolsMenu" anchor="last"/>
</action>
<action id="org.polyfrost.intelliprocessor.action.PreprocessorCommentToggleLineAction"
class="org.polyfrost.intelliprocessor.action.PreprocessorCommentToggleLineAction"
text="Toggle Preprocessor Comments Of Lines &quot;//$$ &quot;"
description="Toggles all preprocessor comments (//$$ ) in the selected lines">
<add-to-group group-id="ToolsMenu" anchor="last"/>
</action>
</actions>
</idea-plugin>
Loading