aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPauline <git@ethanlibs.co>2023-08-26 07:08:33 -0400
committerPauline <git@ethanlibs.co>2023-08-26 07:08:33 -0400
commitcae1da9997d81af994f58ede97049fef4f123f8b (patch)
treedf4c38a5ebb03947741eacfa09963292fd442033
downloadIntelliProcessor-cae1da9997d81af994f58ede97049fef4f123f8b.tar.gz
IntelliProcessor-cae1da9997d81af994f58ede97049fef4f123f8b.tar.bz2
IntelliProcessor-cae1da9997d81af994f58ede97049fef4f123f8b.zip
(release): v1.0.0
Signed-off-by: Pauline <git@ethanlibs.co>
-rw-r--r--.editorconfig2
-rw-r--r--.gitignore399
-rw-r--r--ATTRIBUTION.md4
-rw-r--r--LICENSE202
-rw-r--r--README.md12
-rw-r--r--build.gradle.kts38
-rw-r--r--gradle.properties17
-rw-r--r--gradle/libs.versions.toml7
-rw-r--r--gradle/wrapper/gradle-wrapper.jarbin0 -> 63375 bytes
-rw-r--r--gradle/wrapper/gradle-wrapper.properties7
-rwxr-xr-xgradlew248
-rw-r--r--gradlew.bat92
-rw-r--r--settings.gradle.kts8
-rw-r--r--src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/Preprocessor.kt11
-rw-r--r--src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/PreprocessorCompletion.kt35
-rw-r--r--src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/PreprocessorExtend.kt54
-rw-r--r--src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/PreprocessorFolding.kt51
-rw-r--r--src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/PreprocessorHighlight.kt300
-rw-r--r--src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/PreprocessorImport.kt54
-rw-r--r--src/main/resources/META-INF/plugin.xml37
20 files changed, 1578 insertions, 0 deletions
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..db6b96d
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,2 @@
+[*.{gradle,java}]
+ij_java_class_count_to_use_import_on_demand = 999 \ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f8d108f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,399 @@
+# eclipse
+bin
+*.launch
+.settings
+.metadata
+.classpath
+.project
+
+# idea
+out
+*.ipr
+*.iws
+*.iml
+.idea
+
+# fleet
+.fleet
+
+# gradle
+build
+.gradle
+
+# other
+eclipse
+run
+build - Copy.gradle
+git-story_media
+.vscode
+.devauth
+.DS_STORE
+
+
+# Created by https://www.toptal.com/developers/gitignore/api/java,gradle,forgegradle,kotlin,macos,intellij,intellij+all,eclipse,visualstudiocode
+# Edit at https://www.toptal.com/developers/gitignore?templates=java,gradle,forgegradle,kotlin,macos,intellij,intellij+all,eclipse,visualstudiocode
+
+### Eclipse ###
+.metadata
+bin/
+tmp/
+*.tmp
+*.bak
+*.swp
+*~.nib
+local.properties
+.settings/
+.loadpath
+.recommenders
+
+# External tool builders
+.externalToolBuilders/
+
+# Locally stored "Eclipse launch configurations"
+*.launch
+
+# PyDev specific (Python IDE for Eclipse)
+*.pydevproject
+
+# CDT-specific (C/C++ Development Tooling)
+.cproject
+
+# CDT- autotools
+.autotools
+
+# Java annotation processor (APT)
+.factorypath
+
+# PDT-specific (PHP Development Tools)
+.buildpath
+
+# sbteclipse plugin
+.target
+
+# Tern plugin
+.tern-project
+
+# TeXlipse plugin
+.texlipse
+
+# STS (Spring Tool Suite)
+.springBeans
+
+# Code Recommenders
+.recommenders/
+
+# Annotation Processing
+.apt_generated/
+.apt_generated_test/
+
+# Scala IDE specific (Scala & Java development for Eclipse)
+.cache-main
+.scala_dependencies
+.worksheet
+
+# Uncomment this line if you wish to ignore the project description file.
+# Typically, this file would be tracked if it contains build/dependency configurations:
+#.project
+
+### Eclipse Patch ###
+# Spring Boot Tooling
+.sts4-cache/
+
+### ForgeGradle ###
+# Minecraft client/server files
+run/
+
+### Intellij ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# AWS User-specific
+.idea/**/aws.xml
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# SonarLint plugin
+.idea/sonarlint/
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### Intellij Patch ###
+# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
+
+# *.iml
+# modules.xml
+# .idea/misc.xml
+# *.ipr
+
+# Sonarlint plugin
+# https://plugins.jetbrains.com/plugin/7973-sonarlint
+.idea/**/sonarlint/
+
+# SonarQube Plugin
+# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
+.idea/**/sonarIssues.xml
+
+# Markdown Navigator plugin
+# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
+.idea/**/markdown-navigator.xml
+.idea/**/markdown-navigator-enh.xml
+.idea/**/markdown-navigator/
+
+# Cache file creation bug
+# See https://youtrack.jetbrains.com/issue/JBR-2257
+.idea/$CACHE_FILE$
+
+# CodeStream plugin
+# https://plugins.jetbrains.com/plugin/12206-codestream
+.idea/codestream.xml
+
+# Azure Toolkit for IntelliJ plugin
+# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
+.idea/**/azureSettings.xml
+
+### Intellij+all ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+
+# AWS User-specific
+
+# Generated files
+
+# Sensitive or high-churn files
+
+# Gradle
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+
+# Mongo Explorer plugin
+
+# File-based project format
+
+# IntelliJ
+
+# mpeltonen/sbt-idea plugin
+
+# JIRA plugin
+
+# Cursive Clojure plugin
+
+# SonarLint plugin
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+
+# Editor-based Rest Client
+
+# Android studio 3.1+ serialized cache file
+
+### Intellij+all Patch ###
+# Ignore everything but code style settings and run configurations
+# that are supposed to be shared within teams.
+
+.idea/*
+
+!.idea/codeStyles
+!.idea/runConfigurations
+
+### Java ###
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+replay_pid*
+
+### Kotlin ###
+# Compiled class file
+
+# Log file
+
+# BlueJ files
+
+# Mobile Tools for Java (J2ME)
+
+# Package Files #
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+
+### macOS ###
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+### macOS Patch ###
+# iCloud generated files
+*.icloud
+
+### VisualStudioCode ###
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+!.vscode/*.code-snippets
+
+# Local History for Visual Studio Code
+.history/
+
+# Built Visual Studio Code Extensions
+*.vsix
+
+### VisualStudioCode Patch ###
+# Ignore all local history of files
+.history
+.ionide
+
+# Support for Project snippet scope
+.vscode/*.code-snippets
+
+# Ignore code-workspaces
+*.code-workspace
+
+### Gradle ###
+.gradle
+**/build/
+!src/**/build/
+
+# Ignore Gradle GUI config
+gradle-app.setting
+
+# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
+!gradle-wrapper.jar
+
+# Avoid ignore Gradle wrappper properties
+!gradle-wrapper.properties
+
+!*.api
+
+# Cache of project
+.gradletasknamecache
+
+# Eclipse Gradle plugin generated files
+# Eclipse Core
+.project
+# JDT-specific (Eclipse Java Development Tools)
+.classpath
+
+# End of https://www.toptal.com/developers/gitignore/api/java,gradle,forgegradle,kotlin,macos,intellij,intellij+all,eclipse,visualstudiocode \ No newline at end of file
diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md
new file mode 100644
index 0000000..3adcfb3
--- /dev/null
+++ b/ATTRIBUTION.md
@@ -0,0 +1,4 @@
+# Attribution
+
+- Huge thanks to [IntelliJ-Preprocessor](https://github.com/FalseHonesty/intellij-preprocessor)
+ (inspiration, maintained version under the MIT license) \ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..75e98ef
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2023-present Polyfrost
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License. \ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..2af0494
--- /dev/null
+++ b/README.md
@@ -0,0 +1,12 @@
+# IntelliProcessor
+
+An IntelliJ plugin to add support for the ReplayMod [preprocessor syntax](https://github.com/ReplayMod/Preprocessor)
+
+## Features
+
+* Completion of preprocessor syntax such as `#if`, `#else`, and other variables
+* Auto-completion of preprocessor comment blocks.
+* Highlighting of preprocessor syntax and intellisense of variables
+* Highlighting/intellisense of preprocessor comment blocks.
+* Ability to "fold" preprocessor comment blocks.
+* Auto-remapping within preprocessor comment blocks based on variables.
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..8668e3e
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,38 @@
+fun properties(key: String) = providers.gradleProperty(key)
+
+plugins {
+ id("java")
+ alias(libs.plugins.kotlin)
+ alias(libs.plugins.intellij)
+}
+
+group = properties("pluginGroup").get()
+version = properties("pluginVersion").get()
+
+repositories {
+ maven("https://repo.polyfrost.cc/releases")
+}
+
+kotlin {
+ jvmToolchain(17)
+}
+
+intellij {
+ pluginName = properties("pluginName")
+ version = properties("platformVersion")
+ plugins = properties("platformPlugins").map {
+ it.split(',').map(String::trim).filter(String::isNotEmpty)
+ }
+}
+
+tasks {
+ wrapper {
+ gradleVersion = properties("gradleVersion").get()
+ }
+
+ patchPluginXml {
+ version = properties("pluginVersion")
+ sinceBuild = properties("pluginSinceBuild")
+ untilBuild = properties("pluginUntilBuild")
+ }
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..a9b6fee
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,17 @@
+pluginGroup=cc.polyfrost.sorbet.intelliprocessor
+pluginName=IntelliProcessor
+pluginRepositoryUrl=https://github.com/Polyfrost/IntelliProcessor
+pluginVersion=1.0.0
+pluginSinceBuild=223
+pluginUntilBuild=232.*
+platformType=IC
+platformVersion=2022.3.3
+platformPlugins=com.intellij.java
+gradleVersion=8.3
+kotlin.stdlib.default.dependency=false
+kotlin.code.style=official
+org.gradle.daemon=true
+org.gradle.parallel=true
+org.gradle.configureoncommand=true
+org.gradle.parallel.threads=4
+org.gradle.jvmargs=-Xmx4G \ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
new file mode 100644
index 0000000..30e4cc4
--- /dev/null
+++ b/gradle/libs.versions.toml
@@ -0,0 +1,7 @@
+[versions]
+kotlin = "1.9.0"
+intellij = "1.15.0"
+
+[plugins]
+kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
+intellij = { id = "org.jetbrains.intellij", version.ref = "intellij" }
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..033e24c
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..ac72c34
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..fcb6fca
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command;
+# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+# shell script including quotes and variable substitutions, so put them in
+# double quotes to make sure that they get re-expanded; and
+# * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..93e3f59
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,92 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 0000000..2faaf35
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,8 @@
+pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ maven("https://repo.polyfrost.cc/releases")
+ }
+}
+
+rootProject.name = "IntelliProcessor" \ No newline at end of file
diff --git a/src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/Preprocessor.kt b/src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/Preprocessor.kt
new file mode 100644
index 0000000..a79cb4e
--- /dev/null
+++ b/src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/Preprocessor.kt
@@ -0,0 +1,11 @@
+package cc.polyfrost.sorbet.intelliprocessor
+
+val ALLOWED_TYPES = listOf("JAVA", "KOTLIN")
+
+enum class PreprocessorState {
+ NONE, IF, ELSE
+}
+
+enum class PreprocessorDirective {
+ IF, IFDEF, ELSE, ENDIF
+} \ No newline at end of file
diff --git a/src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/PreprocessorCompletion.kt b/src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/PreprocessorCompletion.kt
new file mode 100644
index 0000000..ce66bc5
--- /dev/null
+++ b/src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/PreprocessorCompletion.kt
@@ -0,0 +1,35 @@
+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
new file mode 100644
index 0000000..2c7ea43
--- /dev/null
+++ b/src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/PreprocessorExtend.kt
@@ -0,0 +1,54 @@
+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
new file mode 100644
index 0000000..1bcbb66
--- /dev/null
+++ b/src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/PreprocessorFolding.kt
@@ -0,0 +1,51 @@
+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
new file mode 100644
index 0000000..2442028
--- /dev/null
+++ b/src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/PreprocessorHighlight.kt
@@ -0,0 +1,300 @@
+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
new file mode 100644
index 0000000..5ccf6f5
--- /dev/null
+++ b/src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/PreprocessorImport.kt
@@ -0,0 +1,54 @@
+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/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml
new file mode 100644
index 0000000..9267a13
--- /dev/null
+++ b/src/main/resources/META-INF/plugin.xml
@@ -0,0 +1,37 @@
+<idea-plugin>
+ <id>cc.polyfrost.sorbet.intelliprocessor</id>
+ <name>IntelliProcessor</name>
+ <vendor url="https://polyfrost.cc">Polyfrost</vendor>
+
+ <description><![CDATA[
+ An IntelliJ plugin to add support for
+ <a href="https://github.com/ReplayMod/preprocessor">preprocessor syntax</a>
+ ]]></description>
+
+ <depends>com.intellij.modules.platform</depends>
+ <depends>com.intellij.modules.java</depends>
+
+ <extensions defaultExtensionNs="com.intellij">
+ <highlightVisitor
+ implementation="cc.polyfrost.sorbet.intelliprocessor.PreprocessorHighlight"
+ />
+ <enterHandlerDelegate
+ implementation="cc.polyfrost.sorbet.intelliprocessor.PreprocessorExtend"
+ />
+ <lang.importOptimizer
+ language="JAVA"
+ implementationClass="cc.polyfrost.sorbet.intelliprocessor.PreprocessorImport"
+ order="first"
+ />
+ <lang.foldingBuilder
+ language="JAVA"
+ implementationClass="cc.polyfrost.sorbet.intelliprocessor.PreprocessorFolding"
+ order="first"
+ />
+ <lang.foldingBuilder
+ language="kotlin"
+ implementationClass="cc.polyfrost.sorbet.intelliprocessor.PreprocessorFolding"
+ order="first"
+ />
+ </extensions>
+</idea-plugin> \ No newline at end of file