diff options
author | Pauline <git@ethanlibs.co> | 2024-04-27 19:59:05 -0400 |
---|---|---|
committer | Pauline <git@ethanlibs.co> | 2024-04-27 19:59:05 -0400 |
commit | d95fe9ab23f4ce19ed1a17b13f995aceefd14fd8 (patch) | |
tree | f01fd22131c5557c6d50ea2dcedd6206c64d7e0b /src | |
parent | cae1da9997d81af994f58ede97049fef4f123f8b (diff) | |
download | IntelliProcessor-d95fe9ab23f4ce19ed1a17b13f995aceefd14fd8.tar.gz IntelliProcessor-d95fe9ab23f4ce19ed1a17b13f995aceefd14fd8.tar.bz2 IntelliProcessor-d95fe9ab23f4ce19ed1a17b13f995aceefd14fd8.zip |
♻️ refactor(plugin): refactor cc -> org and update deps, intellij
Diffstat (limited to 'src')
12 files changed, 556 insertions, 507 deletions
diff --git a/src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/PreprocessorCompletion.kt b/src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/PreprocessorCompletion.kt deleted file mode 100644 index ce66bc5..0000000 --- a/src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/PreprocessorCompletion.kt +++ /dev/null @@ -1,35 +0,0 @@ -package cc.polyfrost.sorbet.intelliprocessor - -import com.intellij.codeInsight.completion.* -import com.intellij.codeInsight.lookup.LookupElementBuilder -import com.intellij.openapi.project.DumbAware -import com.intellij.patterns.PlatformPatterns.psiComment -import com.intellij.patterns.StandardPatterns -import com.intellij.util.ProcessingContext - -class PreprocessorCompletion : CompletionContributor(), DumbAware { - init { - extend( - CompletionType.BASIC, - psiComment().withText( - StandardPatterns.or( - StandardPatterns.string().startsWith("//"), - StandardPatterns.string().startsWith("#") - ) - ), - PreprocessorCompletionProvider - ) - } - - object PreprocessorCompletionProvider : CompletionProvider<CompletionParameters>() { - override fun addCompletions( - parameters: CompletionParameters, - context: ProcessingContext, - result: CompletionResultSet - ) { - for (keyword in KEYWORDS) result.addElement(LookupElementBuilder.create(keyword).bold()) - } - - private val KEYWORDS = listOf("#if", "#else", "#elseif", "#endif", "#ifdef") - } -}
\ No newline at end of file diff --git a/src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/PreprocessorExtend.kt b/src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/PreprocessorExtend.kt deleted file mode 100644 index 2c7ea43..0000000 --- a/src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/PreprocessorExtend.kt +++ /dev/null @@ -1,54 +0,0 @@ -package cc.polyfrost.sorbet.intelliprocessor - -import com.intellij.codeInsight.editorActions.EnterHandler -import com.intellij.codeInsight.editorActions.enter.EnterHandlerDelegate.Result -import com.intellij.codeInsight.editorActions.enter.EnterHandlerDelegateAdapter -import com.intellij.openapi.actionSystem.DataContext -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.PsiFile -import com.intellij.psi.impl.source.tree.PsiCommentImpl -import com.intellij.refactoring.suggested.startOffset -import java.util.Locale - -class PreprocessorExtend : EnterHandlerDelegateAdapter(), DumbAware { - override fun preprocessEnter( - file: PsiFile, - editor: Editor, - caretOffset: Ref<Int>, - caretAdvance: Ref<Int>, - dataContext: DataContext, - originalHandler: EditorActionHandler? - ): Result { - if ( - EnterHandler.getLanguage(dataContext) - ?.associatedFileType - ?.name?.uppercase(Locale.getDefault()) !in ALLOWED_TYPES - ) return Result.Continue - - val caret: Int = caretOffset.get().toInt() - val psiAtOffset = file.findElementAt(caret) - - if (psiAtOffset is PsiCommentImpl) { - if (!psiAtOffset.text.startsWith("//$$")) return Result.Continue - val posInText = caret - psiAtOffset.startOffset - if (posInText < 4) return Result.DefaultForceIndent - - editor.document.insertString(editor.caretModel.offset, "//$$ ") - caretAdvance.set(5) - return Result.DefaultForceIndent - } else if (psiAtOffset?.prevSibling is PsiCommentImpl) { - if (!psiAtOffset.prevSibling.text.startsWith("//$$")) return Result.Continue - val posInText = caret - psiAtOffset.prevSibling.startOffset - if (posInText < 4) return Result.DefaultForceIndent - - editor.document.insertString(editor.caretModel.offset, "//$$ ") - caretAdvance.set(5) - return Result.DefaultForceIndent - } - - return Result.Continue - } -}
\ No newline at end of file diff --git a/src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/PreprocessorFolding.kt b/src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/PreprocessorFolding.kt deleted file mode 100644 index 1bcbb66..0000000 --- a/src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/PreprocessorFolding.kt +++ /dev/null @@ -1,51 +0,0 @@ -package cc.polyfrost.sorbet.intelliprocessor - -import com.intellij.lang.ASTNode -import com.intellij.lang.LanguageCommenters -import com.intellij.lang.folding.FoldingBuilderEx -import com.intellij.lang.folding.FoldingDescriptor -import com.intellij.openapi.editor.Document -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.PsiWhiteSpace -import com.intellij.psi.util.PsiTreeUtil -import com.intellij.refactoring.suggested.endOffset -import com.intellij.refactoring.suggested.startOffset - -class PreprocessorFolding : FoldingBuilderEx(), DumbAware { - override fun getPlaceholderText(node: ASTNode): String { - if (node !is PsiComment) return "...11".also { println("Not a comment? Is $node") } - val directivePrefix = (LanguageCommenters.INSTANCE.forLanguage(node.language).lineCommentPrefix - ?: return "...222".also { println("Null comment prefix?") }) - return (node as ASTNode).text.substring(directivePrefix.length) - } - - override fun buildFoldRegions(root: PsiElement, document: Document, quick: Boolean): Array<FoldingDescriptor> { - val descriptors = mutableListOf<FoldingDescriptor>() - val directivePrefix = (LanguageCommenters.INSTANCE.forLanguage(root.language).lineCommentPrefix - ?: return emptyArray()) + "#" - val allDirectives = PsiTreeUtil.findChildrenOfType(root, PsiComment::class.java) - .filter { it.text.startsWith(directivePrefix) } - - for ((index, directive) in allDirectives.withIndex()) if (directive.text.run { - startsWith(directivePrefix + "if") - || startsWith(directivePrefix + "ifdef") - || startsWith(directivePrefix + "else") - } && index + 1 < allDirectives.size) { - val nextDirective = allDirectives[index + 1] - val endOffset = when { - nextDirective.text.startsWith(directivePrefix + "endif") -> nextDirective.endOffset - nextDirective.prevSibling is PsiWhiteSpace -> nextDirective.prevSibling.startOffset - else -> nextDirective.startOffset - } - - descriptors.add(FoldingDescriptor(directive, TextRange(directive.startOffset, endOffset))) - } - - return descriptors.toTypedArray() - } - - override fun isCollapsedByDefault(node: ASTNode) = false -}
\ No newline at end of file diff --git a/src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/PreprocessorHighlight.kt b/src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/PreprocessorHighlight.kt deleted file mode 100644 index 2442028..0000000 --- a/src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/PreprocessorHighlight.kt +++ /dev/null @@ -1,300 +0,0 @@ -package cc.polyfrost.sorbet.intelliprocessor - -import com.intellij.codeInsight.daemon.impl.HighlightInfo -import com.intellij.codeInsight.daemon.impl.HighlightInfoType -import com.intellij.codeInsight.daemon.impl.HighlightVisitor -import com.intellij.codeInsight.daemon.impl.analysis.HighlightInfoHolder -import com.intellij.lang.Commenter -import com.intellij.lang.LanguageCommenters -import com.intellij.lang.annotation.HighlightSeverity -import com.intellij.openapi.diagnostic.Logger -import com.intellij.openapi.editor.DefaultLanguageHighlighterColors -import com.intellij.openapi.editor.colors.EditorColorsManager -import com.intellij.openapi.editor.colors.TextAttributesKey -import com.intellij.openapi.editor.markup.TextAttributes -import com.intellij.openapi.fileTypes.SyntaxHighlighter -import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory -import com.intellij.openapi.project.DumbAware -import com.intellij.openapi.project.Project -import com.intellij.psi.PsiElement -import com.intellij.psi.PsiFile -import com.intellij.psi.impl.source.tree.PsiCommentImpl -import com.intellij.refactoring.suggested.endOffset -import java.awt.Font -import java.util.* -import java.util.regex.Pattern - -class PreprocessorHighlight(private val project: Project) : HighlightVisitor, DumbAware { - private lateinit var holder: HighlightInfoHolder - private lateinit var commenter: Commenter - private lateinit var highlighter: SyntaxHighlighter - - private var preprocessorState = ArrayDeque<PreprocessorState>() - - override fun suitableForFile(file: PsiFile): Boolean { - return file.fileType.name.uppercase(Locale.getDefault()) in ALLOWED_TYPES - } - - override fun clone(): PreprocessorHighlight { - return PreprocessorHighlight(project) - } - - override fun analyze( - file: PsiFile, - updateWholeFile: Boolean, - holder: HighlightInfoHolder, - action: Runnable - ): Boolean { - this.holder = holder - this.commenter = LanguageCommenters.INSTANCE.forLanguage(file.language) - this.highlighter = SyntaxHighlighterFactory.getSyntaxHighlighter(file.language, file.project, file.virtualFile) - - action.run() - return true - } - - override fun visit(element: PsiElement) { - if (element !is PsiCommentImpl) return - val commentSource = element.text - if (commenter.lineCommentPrefix?.let { - commentSource.startsWith(it) - } != true) return - - val prefixLength = commenter.lineCommentPrefix?.length ?: return - - val comment = commentSource.substring(prefixLength) - if (comment.isEmpty()) return - - EditorColorsManager.getInstance() - - if (comment.startsWith("#")) { - val commentSegments = comment.substring(1).split(WHITESPACES_PATTERN, limit = 2) - - when (val directive = commentSegments[0]) { - "if", "elseif" -> { - if (directive == "elseif") { - val existingIf = preprocessorState.pollFirst() - if (existingIf != PreprocessorState.IF) { - fail( - element, - "Preprocessor directive \"elseif\" must have a preceding \"if\" or \"elseif\"." - ) - return - } - } - - preprocessorState.push(PreprocessorState.IF) - - holder.add(directive.toDirectiveHighlight(element, prefixLength)) - - if (commentSegments.size < 2) { - fail(element, "Preprocessor directive \"$directive\" is missing a condition.", eol = true) - return - } - - val conditionsSource = commentSegments[1] - val conditions = conditionsSource.split(SPLIT_PATTERN) - - var nextStartPos = prefixLength + 3 - for (condition in conditions) { - val trimmedCondition = condition.trim() - - val position = commentSource.indexOf(trimmedCondition, nextStartPos) - nextStartPos = position + trimmedCondition.length - - val conditionMatcher = EXPR_PATTERN.find(trimmedCondition) - - if (conditionMatcher == null || conditionMatcher.groups.size < 4) { - val identifierMatcher = IDENTIFIER_PATTERN.matchEntire(trimmedCondition) - - if (identifierMatcher != null) { - holder.add(identifierMatcher.groups[0]?.toNumericOrVariableHighlight(element, position)) - } else { - holder.add(trimmedCondition.toInvalidConditionErrorHighlight(element, position)) - } - - continue - } - - holder.add(conditionMatcher.groups[1]?.toNumericOrVariableHighlight(element, position)) - holder.add(conditionMatcher.groups[3]?.toNumericOrVariableHighlight(element, position)) - } - } - - "ifdef" -> { - preprocessorState.push(PreprocessorState.IF) - - holder.add(directive.toDirectiveHighlight(element, prefixLength)) - - if (commentSegments.size < 2) { - fail(element, "Preprocessor directive \"ifdef\" is missing an identifier.", eol = true) - return - } - - val idInfo = HighlightInfo - .newHighlightInfo(IDENTIFIER_TYPE) - .range( - element as PsiElement, - element.startOffset + prefixLength + 7, - element.startOffset + prefixLength + 7 + commentSegments[1].length - ) - .textAttributes(IDENTIFIER_ATTRIBUTES) - .create() - - holder.add(idInfo) - } - - "else" -> { - val state = preprocessorState.pollFirst() - preprocessorState.push(PreprocessorState.ELSE) - - if (state != PreprocessorState.IF) { - fail(element, "Preprocessor directive \"else\" must have an opening if.") - return - } - - if (commentSegments.size > 1) { - fail(element, "Preprocessor directive \"else\" does not require any arguments.") - return - } - - holder.add(directive.toDirectiveHighlight(element, prefixLength)) - } - - "endif" -> { - val state = preprocessorState.pollFirst() - - if (state != PreprocessorState.IF && state != PreprocessorState.ELSE) { - fail(element, "Preprocessor directive \"endif\" must have an opening if.") - return - } - - if (commentSegments.size > 1) { - fail(element, "Preprocessor directive \"endif\" does not require any arguments.") - return - } - - holder.add(directive.toDirectiveHighlight(element, prefixLength)) - } - - else -> { - fail(element, "Unknown preprocessor directive \"$directive\"") - } - } - } else if (comment.startsWith("$$")) { - holder.add("$$".toDirectiveHighlight(element, prefixLength)) - - highlightCodeBlock(element, element.startOffset + prefixLength + 2, comment.substring(2)) - } - } - - private fun highlightCodeBlock(element: PsiCommentImpl, startOffset: Int, text: String) { - val lexer = highlighter.highlightingLexer - - lexer.start(text) - var token = lexer.tokenType - - while (token != null) { - val attributes = highlighter.getTokenHighlights(token) - .fold(TextAttributes(null, null, null, null, 0)) { first, second -> - TextAttributes.merge(first, SCHEME.getAttributes(second)) - } - - val directiveInfo = HighlightInfo - .newHighlightInfo(HighlightInfoType.INJECTED_LANGUAGE_FRAGMENT) - .range( - element as PsiElement, - startOffset + lexer.tokenStart, - startOffset + lexer.tokenEnd - ) - .textAttributes(attributes) - .create() - - holder.add(directiveInfo) - - lexer.advance() - token = lexer.tokenType - } - } - - private fun fail(element: PsiElement, text: String, eol: Boolean = false) { - val info = HighlightInfo - .newHighlightInfo(HighlightInfoType.ERROR) - .descriptionAndTooltip(text) - .apply { - if (eol) { - endOfLine() - range(element.endOffset, element.endOffset) - } else { - range(element) - } - } - .create() - - holder.add(info) - } - - private fun MatchGroup.toNumericOrVariableHighlight(element: PsiCommentImpl, offset: Int = 0): HighlightInfo? { - val builder = if (value.trim().toIntOrNull() != null) { - HighlightInfo - .newHighlightInfo(NUMBER_TYPE) - .textAttributes(NUMBER_ATTRIBUTES) - } else { - HighlightInfo - .newHighlightInfo(IDENTIFIER_TYPE) - .textAttributes(IDENTIFIER_ATTRIBUTES) - } - - return builder - .range(element, element.startOffset + offset + range.first, element.startOffset + offset + range.last + 1) - .create() - } - - private fun String.toDirectiveHighlight(element: PsiCommentImpl, offset: Int = 0): HighlightInfo? { - return HighlightInfo - .newHighlightInfo(DIRECTIVE_TYPE) - .textAttributes(DIRECTIVE_ATTRIBUTES) - .range(element, element.startOffset + offset, element.startOffset + offset + 1 + length) - .create() - } - - private fun String.toInvalidConditionErrorHighlight(element: PsiCommentImpl, offset: Int = 0): HighlightInfo? { - return HighlightInfo - .newHighlightInfo(HighlightInfoType.ERROR) - .range(element, element.startOffset + offset, element.startOffset + offset + length) - .descriptionAndTooltip("Invalid condition \"$this\"") - .create() - } - - companion object { - private val BOLD_ATTRIBUTE = TextAttributes(null, null, null, null, Font.BOLD) - val SCHEME = EditorColorsManager.getInstance().globalScheme - - private val DIRECTIVE_COLOR: TextAttributesKey = DefaultLanguageHighlighterColors.KEYWORD - val DIRECTIVE_ATTRIBUTES: TextAttributes = - TextAttributes.merge(SCHEME.getAttributes(DIRECTIVE_COLOR), BOLD_ATTRIBUTE) - val DIRECTIVE_TYPE = HighlightInfoType.HighlightInfoTypeImpl(HighlightSeverity.INFORMATION, DIRECTIVE_COLOR) - - private val OPERATOR_COLOR: TextAttributesKey = DefaultLanguageHighlighterColors.OPERATION_SIGN - val OPERATOR_ATTRIBUTES = SCHEME.getAttributes(OPERATOR_COLOR) - val OPERATOR_TYPE = HighlightInfoType.HighlightInfoTypeImpl(HighlightSeverity.INFORMATION, OPERATOR_COLOR) - - private val IDENTIFIER_COLOR: TextAttributesKey = DefaultLanguageHighlighterColors.IDENTIFIER - val IDENTIFIER_ATTRIBUTES: TextAttributes = - TextAttributes.merge(SCHEME.getAttributes(IDENTIFIER_COLOR), BOLD_ATTRIBUTE) - val IDENTIFIER_TYPE = HighlightInfoType.HighlightInfoTypeImpl(HighlightSeverity.INFORMATION, IDENTIFIER_COLOR) - - private val NUMBER_COLOR: TextAttributesKey = DefaultLanguageHighlighterColors.NUMBER - val NUMBER_ATTRIBUTES: TextAttributes = TextAttributes.merge(SCHEME.getAttributes(NUMBER_COLOR), BOLD_ATTRIBUTE) - val NUMBER_TYPE = HighlightInfoType.HighlightInfoTypeImpl(HighlightSeverity.INFORMATION, NUMBER_COLOR) - - private val LOGGER: Logger = Logger.getInstance(PreprocessorHighlight::class.java) - - private val WHITESPACES_PATTERN = "\\s+".toRegex() - private val EXPR_PATTERN = "(.+)(==|!=|<=|>=|<|>)(.+)".toRegex() - private val IDENTIFIER_PATTERN = "[A-Za-z0-9]+".toRegex() - private val OR_PATTERN = Pattern.quote("||") - private val AND_PATTERN = Pattern.quote("&&") - private val SPLIT_PATTERN = Pattern.compile("$OR_PATTERN|$AND_PATTERN") - } -}
\ No newline at end of file diff --git a/src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/PreprocessorImport.kt b/src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/PreprocessorImport.kt deleted file mode 100644 index 5ccf6f5..0000000 --- a/src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/PreprocessorImport.kt +++ /dev/null @@ -1,54 +0,0 @@ -package cc.polyfrost.sorbet.intelliprocessor - -import com.intellij.lang.ImportOptimizer -import com.intellij.lang.LanguageImportStatements -import com.intellij.lang.java.JavaImportOptimizer -import com.intellij.openapi.util.EmptyRunnable -import com.intellij.psi.* -import com.intellij.psi.codeStyle.JavaCodeStyleManager -import com.intellij.psi.impl.source.tree.PsiCommentImpl - -class PreprocessorImport : ImportOptimizer { - override fun supports(file: PsiFile): Boolean { - return file is PsiJavaFile - } - - override fun processFile(file: PsiFile): Runnable { - if (file !is PsiJavaFile) return EmptyRunnable.getInstance() - val imports = file.importList ?: return EmptyRunnable.getInstance() - - if (!hasPreprocessorDirectives(imports)) - return LanguageImportStatements.INSTANCE - .allForLanguage(file.language) - .first { it !is JavaImportOptimizer } - .processFile(file) - - val optimizedImportList = JavaCodeStyleManager - .getInstance(file.project) - .prepareOptimizeImportsResult(file) - - return Runnable { - val manager = PsiDocumentManager.getInstance(file.project) - val document = manager.getDocument(file) - if (document != null) manager.commitDocument(document) - - for (import in imports.importStatements) - if (optimizedImportList.findSingleClassImportStatement(import.qualifiedName) == null) - import.delete() - - if (imports.firstChild is PsiWhiteSpace) imports.firstChild.delete() - } - } - - private fun hasPreprocessorDirectives(imports: PsiImportList): Boolean { - var import = imports.firstChild - - while (import != null) { - if (import is PsiCommentImpl && import.text.startsWith("//#")) - return true - else import = import.nextSibling - } - - return false - } -}
\ No newline at end of file diff --git a/src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/Preprocessor.kt b/src/main/kotlin/org/polyfrost/sorbet/intelliprocessor/Preprocessor.kt index a79cb4e..03cb762 100644 --- a/src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/Preprocessor.kt +++ b/src/main/kotlin/org/polyfrost/sorbet/intelliprocessor/Preprocessor.kt @@ -1,11 +1,16 @@ -package cc.polyfrost.sorbet.intelliprocessor +package org.polyfrost.sorbet.intelliprocessor val ALLOWED_TYPES = listOf("JAVA", "KOTLIN") enum class PreprocessorState { - NONE, IF, ELSE + NONE, + IF, + ELSE, } enum class PreprocessorDirective { - IF, IFDEF, ELSE, ENDIF -}
\ No newline at end of file + IF, + IFDEF, + ELSE, + ENDIF, +} diff --git a/src/main/kotlin/org/polyfrost/sorbet/intelliprocessor/PreprocessorCompletion.kt b/src/main/kotlin/org/polyfrost/sorbet/intelliprocessor/PreprocessorCompletion.kt new file mode 100644 index 0000000..b5f8417 --- /dev/null +++ b/src/main/kotlin/org/polyfrost/sorbet/intelliprocessor/PreprocessorCompletion.kt @@ -0,0 +1,35 @@ +package org.polyfrost.sorbet.intelliprocessor + +import com.intellij.codeInsight.completion.* +import com.intellij.codeInsight.lookup.LookupElementBuilder +import com.intellij.openapi.project.DumbAware +import com.intellij.patterns.PlatformPatterns.psiComment +import com.intellij.patterns.StandardPatterns +import com.intellij.util.ProcessingContext + +class PreprocessorCompletion : CompletionContributor(), DumbAware { + init { + extend( + CompletionType.BASIC, + psiComment().withText( + StandardPatterns.or( + StandardPatterns.string().startsWith("//"), + StandardPatterns.string().startsWith("#"), + ), + ), + PreprocessorCompletionProvider, + ) + } + + object PreprocessorCompletionProvider : CompletionProvider<CompletionParameters>() { + override fun addCompletions( + parameters: CompletionParameters, + context: ProcessingContext, + result: CompletionResultSet, + ) { + for (keyword in KEYWORDS) result.addElement(LookupElementBuilder.create(keyword).bold()) + } + + private val KEYWORDS = listOf("#if", "#else", "#elseif", "#endif", "#ifdef") + } +} diff --git a/src/main/kotlin/org/polyfrost/sorbet/intelliprocessor/PreprocessorExtend.kt b/src/main/kotlin/org/polyfrost/sorbet/intelliprocessor/PreprocessorExtend.kt new file mode 100644 index 0000000..db59737 --- /dev/null +++ b/src/main/kotlin/org/polyfrost/sorbet/intelliprocessor/PreprocessorExtend.kt @@ -0,0 +1,56 @@ +package org.polyfrost.sorbet.intelliprocessor + +import com.intellij.codeInsight.editorActions.EnterHandler +import com.intellij.codeInsight.editorActions.enter.EnterHandlerDelegate.Result +import com.intellij.codeInsight.editorActions.enter.EnterHandlerDelegateAdapter +import com.intellij.openapi.actionSystem.DataContext +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.PsiFile +import com.intellij.psi.impl.source.tree.PsiCommentImpl +import com.intellij.refactoring.suggested.startOffset +import java.util.Locale + +class PreprocessorExtend : EnterHandlerDelegateAdapter(), DumbAware { + override fun preprocessEnter( + file: PsiFile, + editor: Editor, + caretOffset: Ref<Int>, + caretAdvance: Ref<Int>, + dataContext: DataContext, + originalHandler: EditorActionHandler?, + ): Result { + if ( + EnterHandler.getLanguage(dataContext) + ?.associatedFileType + ?.name?.uppercase(Locale.getDefault()) !in ALLOWED_TYPES + ) { + return Result.Continue + } + + val caret: Int = caretOffset.get().toInt() + val psiAtOffset = file.findElementAt(caret) + + if (psiAtOffset is PsiCommentImpl) { + if (!psiAtOffset.text.startsWith("//$$")) return Result.Continue + val posInText = caret - psiAtOffset.startOffset + if (posInText < 4) return Result.DefaultForceIndent + + editor.document.insertString(editor.caretModel.offset, "//$$ ") + caretAdvance.set(5) + return Result.DefaultForceIndent + } else if (psiAtOffset?.prevSibling is PsiCommentImpl) { + if (!psiAtOffset.prevSibling.text.startsWith("//$$")) return Result.Continue + val posInText = caret - psiAtOffset.prevSibling.startOffset + if (posInText < 4) return Result.DefaultForceIndent + + editor.document.insertString(editor.caretModel.offset, "//$$ ") + caretAdvance.set(5) + return Result.DefaultForceIndent + } + + return Result.Continue + } +} diff --git a/src/main/kotlin/org/polyfrost/sorbet/intelliprocessor/PreprocessorFolding.kt b/src/main/kotlin/org/polyfrost/sorbet/intelliprocessor/PreprocessorFolding.kt new file mode 100644 index 0000000..25d5ba0 --- /dev/null +++ b/src/main/kotlin/org/polyfrost/sorbet/intelliprocessor/PreprocessorFolding.kt @@ -0,0 +1,63 @@ +package org.polyfrost.sorbet.intelliprocessor + +import com.intellij.lang.ASTNode +import com.intellij.lang.LanguageCommenters +import com.intellij.lang.folding.FoldingBuilderEx +import com.intellij.lang.folding.FoldingDescriptor +import com.intellij.openapi.editor.Document +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.PsiWhiteSpace +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.refactoring.suggested.endOffset +import com.intellij.refactoring.suggested.startOffset + +class PreprocessorFolding : FoldingBuilderEx(), DumbAware { + override fun getPlaceholderText(node: ASTNode): String { + if (node !is PsiComment) return "...11".also { println("Not a comment? Is $node") } + val directivePrefix = ( + LanguageCommenters.INSTANCE.forLanguage(node.language).lineCommentPrefix + ?: return "...222".also { println("Null comment prefix?") } + ) + return (node as ASTNode).text.substring(directivePrefix.length) + } + + override fun buildFoldRegions( + root: PsiElement, + document: Document, + quick: Boolean, + ): Array<FoldingDescriptor> { + val descriptors = mutableListOf<FoldingDescriptor>() + val directivePrefix = + ( + LanguageCommenters.INSTANCE.forLanguage(root.language).lineCommentPrefix + ?: return emptyArray() + ) + "#" + val allDirectives = + PsiTreeUtil.findChildrenOfType(root, PsiComment::class.java) + .filter { it.text.startsWith(directivePrefix) } + + for ((index, directive) in allDirectives.withIndex()) if (directive.text.run { + startsWith(directivePrefix + "if") || + startsWith(directivePrefix + "ifdef") || + startsWith(directivePrefix + "else") + } && index + 1 < allDirectives.size + ) { + val nextDirective = allDirectives[index + 1] + val endOffset = + when { + nextDirective.text.startsWith(directivePrefix + "endif") -> nextDirective.endOffset + nextDirective.prevSibling is PsiWhiteSpace -> nextDirective.prevSibling.startOffset + else -> nextDirective.startOffset + } + + descriptors.add(FoldingDescriptor(directive, TextRange(directive.startOffset, endOffset))) + } + + return descriptors.toTypedArray() + } + + override fun isCollapsedByDefault(node: ASTNode) = false +} diff --git a/src/main/kotlin/org/polyfrost/sorbet/intelliprocessor/PreprocessorHighlight.kt b/src/main/kotlin/org/polyfrost/sorbet/intelliprocessor/PreprocessorHighlight.kt new file mode 100644 index 0000000..9eba31b --- /dev/null +++ b/src/main/kotlin/org/polyfrost/sorbet/intelliprocessor/PreprocessorHighlight.kt @@ -0,0 +1,325 @@ +package org.polyfrost.sorbet.intelliprocessor + +import com.intellij.codeInsight.daemon.impl.HighlightInfo +import com.intellij.codeInsight.daemon.impl.HighlightInfoType +import com.intellij.codeInsight.daemon.impl.HighlightVisitor +import com.intellij.codeInsight.daemon.impl.analysis.HighlightInfoHolder +import com.intellij.lang.Commenter +import com.intellij.lang.LanguageCommenters +import com.intellij.lang.annotation.HighlightSeverity +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.editor.DefaultLanguageHighlighterColors +import com.intellij.openapi.editor.colors.EditorColorsManager +import com.intellij.openapi.editor.colors.TextAttributesKey +import com.intellij.openapi.editor.markup.TextAttributes +import com.intellij.openapi.fileTypes.SyntaxHighlighter +import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory +import com.intellij.openapi.project.DumbAware +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.psi.impl.source.tree.PsiCommentImpl +import com.intellij.refactoring.suggested.endOffset +import java.awt.Font +import java.util.* +import java.util.regex.Pattern + +class PreprocessorHighlight(private val project: Project) : HighlightVisitor, DumbAware { + private lateinit var holder: HighlightInfoHolder + private lateinit var commenter: Commenter + private lateinit var highlighter: SyntaxHighlighter + + private var preprocessorState = ArrayDeque<PreprocessorState>() + + override fun suitableForFile(file: PsiFile): Boolean { + return file.fileType.name.uppercase(Locale.getDefault()) in ALLOWED_TYPES + } + + override fun clone(): PreprocessorHighlight { + return PreprocessorHighlight(project) + } + + override fun analyze( + file: PsiFile, + updateWholeFile: Boolean, + holder: HighlightInfoHolder, + action: Runnable, + ): Boolean { + this.holder = holder + this.commenter = LanguageCommenters.INSTANCE.forLanguage(file.language) + this.highlighter = SyntaxHighlighterFactory.getSyntaxHighlighter(file.language, file.project, file.virtualFile) + + action.run() + return true + } + + override fun visit(element: PsiElement) { + if (element !is PsiCommentImpl) return + val commentSource = element.text + if (commenter.lineCommentPrefix?.let { + commentSource.startsWith(it) + } != true + ) { + return + } + + val prefixLength = commenter.lineCommentPrefix?.length ?: return + + val comment = commentSource.substring(prefixLength) + if (comment.isEmpty()) return + + EditorColorsManager.getInstance() + + if (comment.startsWith("#")) { + val commentSegments = comment.substring(1).split(WHITESPACES_PATTERN, limit = 2) + + when (val directive = commentSegments[0]) { + "if", "elseif" -> { + if (directive == "elseif") { + val existingIf = preprocessorState.pollFirst() + if (existingIf != PreprocessorState.IF) { + fail( + element, + "Preprocessor directive \"elseif\" must have a preceding \"if\" or \"elseif\".", + ) + return + } + } + + preprocessorState.push(PreprocessorState.IF) + + holder.add(directive.toDirectiveHighlight(element, prefixLength)) + + if (commentSegments.size < 2) { + fail(element, "Preprocessor directive \"$directive\" is missing a condition.", eol = true) + return + } + + val conditionsSource = commentSegments[1] + val conditions = conditionsSource.split(SPLIT_PATTERN) + + var nextStartPos = prefixLength + 3 + for (condition in conditions) { + val trimmedCondition = condition.trim() + + val position = commentSource.indexOf(trimmedCondition, nextStartPos) + nextStartPos = position + trimmedCondition.length + + val conditionMatcher = EXPR_PATTERN.find(trimmedCondition) + + if (conditionMatcher == null || conditionMatcher.groups.size < 4) { + val identifierMatcher = IDENTIFIER_PATTERN.matchEntire(trimmedCondition) + + if (identifierMatcher != null) { + holder.add(identifierMatcher.groups[0]?.toNumericOrVariableHighlight(element, position)) + } else { + holder.add(trimmedCondition.toInvalidConditionErrorHighlight(element, position)) + } + + continue + } + + holder.add(conditionMatcher.groups[1]?.toNumericOrVariableHighlight(element, position)) + holder.add(conditionMatcher.groups[3]?.toNumericOrVariableHighlight(element, position)) + } + } + + "ifdef" -> { + preprocessorState.push(PreprocessorState.IF) + + holder.add(directive.toDirectiveHighlight(element, prefixLength)) + + if (commentSegments.size < 2) { + fail(element, "Preprocessor directive \"ifdef\" is missing an identifier.", eol = true) + return + } + + val idInfo = + HighlightInfo + .newHighlightInfo(IDENTIFIER_TYPE) + .range( + element as PsiElement, + element.startOffset + prefixLength + 7, + element.startOffset + prefixLength + 7 + commentSegments[1].length, + ) + .textAttributes(IDENTIFIER_ATTRIBUTES) + .create() + + holder.add(idInfo) + } + + "else" -> { + val state = preprocessorState.pollFirst() + preprocessorState.push(PreprocessorState.ELSE) + + if (state != PreprocessorState.IF) { + fail(element, "Preprocessor directive \"else\" must have an opening if.") + return + } + + if (commentSegments.size > 1) { + fail(element, "Preprocessor directive \"else\" does not require any arguments.") + return + } + + holder.add(directive.toDirectiveHighlight(element, prefixLength)) + } + + "endif" -> { + val state = preprocessorState.pollFirst() + + if (state != PreprocessorState.IF && state != PreprocessorState.ELSE) { + fail(element, "Preprocessor directive \"endif\" must have an opening if.") + return + } + + if (commentSegments.size > 1) { + fail(element, "Preprocessor directive \"endif\" does not require any arguments.") + return + } + + holder.add(directive.toDirectiveHighlight(element, prefixLength)) + } + + else -> { + fail(element, "Unknown preprocessor directive \"$directive\"") + } + } + } else if (comment.startsWith("$$")) { + holder.add("$$".toDirectiveHighlight(element, prefixLength)) + + highlightCodeBlock(element, element.startOffset + prefixLength + 2, comment.substring(2)) + } + } + + private fun highlightCodeBlock( + element: PsiCommentImpl, + startOffset: Int, + text: String, + ) { + val lexer = highlighter.highlightingLexer + + lexer.start(text) + var token = lexer.tokenType + + while (token != null) { + val attributes = + highlighter.getTokenHighlights(token) + .fold(TextAttributes(null, null, null, null, 0)) { first, second -> + TextAttributes.merge(first, SCHEME.getAttributes(second)) + } + + val directiveInfo = + HighlightInfo + .newHighlightInfo(HighlightInfoType.INJECTED_LANGUAGE_FRAGMENT) + .range( + element as PsiElement, + startOffset + lexer.tokenStart, + startOffset + lexer.tokenEnd, + ) + .textAttributes(attributes) + .create() + + holder.add(directiveInfo) + + lexer.advance() + token = lexer.tokenType + } + } + + private fun fail( + element: PsiElement, + text: String, + eol: Boolean = false, + ) { + val info = + HighlightInfo + .newHighlightInfo(HighlightInfoType.ERROR) + .descriptionAndTooltip(text) + .apply { + if (eol) { + endOfLine() + range(element.endOffset, element.endOffset) + } else { + range(element) + } + } + .create() + + holder.add(info) + } + + private fun MatchGroup.toNumericOrVariableHighlight( + element: PsiCommentImpl, + offset: Int = 0, + ): HighlightInfo? { + val builder = + if (value.trim().toIntOrNull() != null) { + HighlightInfo + .newHighlightInfo(NUMBER_TYPE) + .textAttributes(NUMBER_ATTRIBUTES) + } else { + HighlightInfo + .newHighlightInfo(IDENTIFIER_TYPE) + .textAttributes(IDENTIFIER_ATTRIBUTES) + } + + return builder + .range(element, element.startOffset + offset + range.first, element.startOffset + offset + range.last + 1) + .create() + } + + private fun String.toDirectiveHighlight( + element: PsiCommentImpl, + offset: Int = 0, + ): HighlightInfo? { + return HighlightInfo + .newHighlightInfo(DIRECTIVE_TYPE) + .textAttributes(DIRECTIVE_ATTRIBUTES) + .range(element, element.startOffset + offset, element.startOffset + offset + 1 + length) + .create() + } + + private fun String.toInvalidConditionErrorHighlight( + element: PsiCommentImpl, + offset: Int = 0, + ): HighlightInfo? { + return HighlightInfo + .newHighlightInfo(HighlightInfoType.ERROR) + .range(element, element.startOffset + offset, element.startOffset + offset + length) + .descriptionAndTooltip("Invalid condition \"$this\"") + .create() + } + + companion object { + private val BOLD_ATTRIBUTE = TextAttributes(null, null, null, null, Font.BOLD) + val SCHEME = EditorColorsManager.getInstance().globalScheme + + private val DIRECTIVE_COLOR: TextAttributesKey = DefaultLanguageHighlighterColors.KEYWORD + val DIRECTIVE_ATTRIBUTES: TextAttributes = + TextAttributes.merge(SCHEME.getAttributes(DIRECTIVE_COLOR), BOLD_ATTRIBUTE) + val DIRECTIVE_TYPE = HighlightInfoType.HighlightInfoTypeImpl(HighlightSeverity.INFORMATION, DIRECTIVE_COLOR) + + private val OPERATOR_COLOR: TextAttributesKey = DefaultLanguageHighlighterColors.OPERATION_SIGN + val OPERATOR_ATTRIBUTES = SCHEME.getAttributes(OPERATOR_COLOR) + val OPERATOR_TYPE = HighlightInfoType.HighlightInfoTypeImpl(HighlightSeverity.INFORMATION, OPERATOR_COLOR) + + private val IDENTIFIER_COLOR: TextAttributesKey = DefaultLanguageHighlighterColors.IDENTIFIER + val IDENTIFIER_ATTRIBUTES: TextAttributes = + TextAttributes.merge(SCHEME.getAttributes(IDENTIFIER_COLOR), BOLD_ATTRIBUTE) + val IDENTIFIER_TYPE = HighlightInfoType.HighlightInfoTypeImpl(HighlightSeverity.INFORMATION, IDENTIFIER_COLOR) + + private val NUMBER_COLOR: TextAttributesKey = DefaultLanguageHighlighterColors.NUMBER + val NUMBER_ATTRIBUTES: TextAttributes = TextAttributes.merge(SCHEME.getAttributes(NUMBER_COLOR), BOLD_ATTRIBUTE) + val NUMBER_TYPE = HighlightInfoType.HighlightInfoTypeImpl(HighlightSeverity.INFORMATION, NUMBER_COLOR) + + private val LOGGER: Logger = Logger.getInstance(PreprocessorHighlight::class.java) + + private val WHITESPACES_PATTERN = "\\s+".toRegex() + private val EXPR_PATTERN = "(.+)(==|!=|<=|>=|<|>)(.+)".toRegex() + private val IDENTIFIER_PATTERN = "[A-Za-z0-9]+".toRegex() + private val OR_PATTERN = Pattern.quote("||") + private val AND_PATTERN = Pattern.quote("&&") + private val SPLIT_PATTERN = Pattern.compile("$OR_PATTERN|$AND_PATTERN") + } +} diff --git a/src/main/kotlin/org/polyfrost/sorbet/intelliprocessor/PreprocessorImport.kt b/src/main/kotlin/org/polyfrost/sorbet/intelliprocessor/PreprocessorImport.kt new file mode 100644 index 0000000..1072ee0 --- /dev/null +++ b/src/main/kotlin/org/polyfrost/sorbet/intelliprocessor/PreprocessorImport.kt @@ -0,0 +1,59 @@ +package org.polyfrost.sorbet.intelliprocessor + +import com.intellij.lang.ImportOptimizer +import com.intellij.lang.LanguageImportStatements +import com.intellij.lang.java.JavaImportOptimizer +import com.intellij.openapi.util.EmptyRunnable +import com.intellij.psi.* +import com.intellij.psi.codeStyle.JavaCodeStyleManager +import com.intellij.psi.impl.source.tree.PsiCommentImpl + +class PreprocessorImport : ImportOptimizer { + override fun supports(file: PsiFile): Boolean { + return file is PsiJavaFile + } + + override fun processFile(file: PsiFile): Runnable { + if (file !is PsiJavaFile) return EmptyRunnable.getInstance() + val imports = file.importList ?: return EmptyRunnable.getInstance() + + if (!hasPreprocessorDirectives(imports)) { + return LanguageImportStatements.INSTANCE + .allForLanguage(file.language) + .first { it !is JavaImportOptimizer } + .processFile(file) + } + + val optimizedImportList = + JavaCodeStyleManager + .getInstance(file.project) + .prepareOptimizeImportsResult(file) + + return Runnable { + val manager = PsiDocumentManager.getInstance(file.project) + val document = manager.getDocument(file) + if (document != null) manager.commitDocument(document) + + for (import in imports.importStatements) + if (optimizedImportList.findSingleClassImportStatement(import.qualifiedName) == null) { + import.delete() + } + + if (imports.firstChild is PsiWhiteSpace) imports.firstChild.delete() + } + } + + private fun hasPreprocessorDirectives(imports: PsiImportList): Boolean { + var import = imports.firstChild + + while (import != null) { + if (import is PsiCommentImpl && import.text.startsWith("//#")) { + return true + } else { + import = import.nextSibling + } + } + + return false + } +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 9267a13..d20684e 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -1,10 +1,10 @@ <idea-plugin> - <id>cc.polyfrost.sorbet.intelliprocessor</id> + <id>org.polyfrost.sorbet.intelliprocessor</id> <name>IntelliProcessor</name> - <vendor url="https://polyfrost.cc">Polyfrost</vendor> + <vendor url="https://polyfrost.org">Polyfrost</vendor> <description><![CDATA[ - An IntelliJ plugin to add support for + An IntelliJ IDEA plugin to add support for <a href="https://github.com/ReplayMod/preprocessor">preprocessor syntax</a> ]]></description> @@ -13,25 +13,25 @@ <extensions defaultExtensionNs="com.intellij"> <highlightVisitor - implementation="cc.polyfrost.sorbet.intelliprocessor.PreprocessorHighlight" + implementation="org.polyfrost.sorbet.intelliprocessor.PreprocessorHighlight" /> <enterHandlerDelegate - implementation="cc.polyfrost.sorbet.intelliprocessor.PreprocessorExtend" + implementation="org.polyfrost.sorbet.intelliprocessor.PreprocessorExtend" /> <lang.importOptimizer language="JAVA" - implementationClass="cc.polyfrost.sorbet.intelliprocessor.PreprocessorImport" + implementationClass="org.polyfrost.sorbet.intelliprocessor.PreprocessorImport" order="first" /> <lang.foldingBuilder language="JAVA" - implementationClass="cc.polyfrost.sorbet.intelliprocessor.PreprocessorFolding" + implementationClass="org.polyfrost.sorbet.intelliprocessor.PreprocessorFolding" order="first" /> <lang.foldingBuilder language="kotlin" - implementationClass="cc.polyfrost.sorbet.intelliprocessor.PreprocessorFolding" + implementationClass="org.polyfrost.sorbet.intelliprocessor.PreprocessorFolding" order="first" /> </extensions> -</idea-plugin>
\ No newline at end of file +</idea-plugin> |