diff options
-rw-r--r-- | .editorconfig | 2 | ||||
-rw-r--r-- | .gitignore | 399 | ||||
-rw-r--r-- | ATTRIBUTION.md | 4 | ||||
-rw-r--r-- | LICENSE | 202 | ||||
-rw-r--r-- | README.md | 12 | ||||
-rw-r--r-- | build.gradle.kts | 38 | ||||
-rw-r--r-- | gradle.properties | 17 | ||||
-rw-r--r-- | gradle/libs.versions.toml | 7 | ||||
-rw-r--r-- | gradle/wrapper/gradle-wrapper.jar | bin | 0 -> 63375 bytes | |||
-rw-r--r-- | gradle/wrapper/gradle-wrapper.properties | 7 | ||||
-rwxr-xr-x | gradlew | 248 | ||||
-rw-r--r-- | gradlew.bat | 92 | ||||
-rw-r--r-- | settings.gradle.kts | 8 | ||||
-rw-r--r-- | src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/Preprocessor.kt | 11 | ||||
-rw-r--r-- | src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/PreprocessorCompletion.kt | 35 | ||||
-rw-r--r-- | src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/PreprocessorExtend.kt | 54 | ||||
-rw-r--r-- | src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/PreprocessorFolding.kt | 51 | ||||
-rw-r--r-- | src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/PreprocessorHighlight.kt | 300 | ||||
-rw-r--r-- | src/main/kotlin/cc/polyfrost/sorbet/intelliprocessor/PreprocessorImport.kt | 54 | ||||
-rw-r--r-- | src/main/resources/META-INF/plugin.xml | 37 |
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 @@ -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 Binary files differnew file mode 100644 index 0000000..033e24c --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.jar 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 @@ -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 |