diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bb171cd..13a1d42 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -36,7 +36,7 @@ jobs: strategy: fail-fast: false matrix: - product: [ "IC-2023.1", "IC-2023.2", "IC-2023.3", "IC-2024.1" ] + product: [ "IC-2023.1", "IC-2023.2", "IC-2023.3", "IC-2024.1", "IC-2024.2" ] max-parallel: 5 env: PRODUCT_NAME: ${{ matrix.product }} diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 901d109..448b918 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -34,7 +34,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - product: [ "IC-2023.1", "IC-2023.2", "IC-2023.3", "IC-2024.1" ] + product: [ "IC-2023.1", "IC-2023.2", "IC-2023.3", "IC-2024.1", "IC-2024.2" ] max-parallel: 5 env: PRODUCT_NAME: ${{ matrix.product }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ff67a22..65a8f49 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ jobs: release: strategy: matrix: - product: [ "IC-2023.1", "IC-2023.2", "IC-2023.3", "IC-2024.1" ] + product: [ "IC-2023.1", "IC-2023.2", "IC-2023.3", "IC-2024.1", "IC-2024.2" ] max-parallel: 1 env: PRODUCT_NAME: ${{ matrix.product }} diff --git a/build.gradle.kts b/build.gradle.kts index 0dede27..7875fa4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -75,10 +75,21 @@ val plugins = listOf( apiVersion = "1.6" ), bundledDependencies = listOf("com.intellij.java", "org.jetbrains.kotlin") + ), + PluginDescriptor( + since = "242", + until = "242.*", + platformVersion = "2024.2", + platformType = PlatformType.IdeaCommunity, + sourceFolder = "IC-242", + kotlin = KotlinOptions( + apiVersion = "1.6" + ), + bundledDependencies = listOf("com.intellij.java", "org.jetbrains.kotlin") ) ) -val defaultProductName = "IC-2023.1" +val defaultProductName = "IC-2024.2" val productName = System.getenv("PRODUCT_NAME") ?: defaultProductName val maybeGithubRunNumber = System.getenv("GITHUB_RUN_NUMBER")?.toInt() val descriptor = plugins.first { it.getSDKVersion() == productName } @@ -89,7 +100,7 @@ val pluginGroup: String by project // `pluginName_` variable ends with `_` because of the collision with Kotlin magic getter in the `intellij` closure. // Read more about the issue: https://github.com/JetBrains/intellij-platform-plugin-template/issues/29 val pluginName_: String by project -val pluginVersion: String = pluginVersion(major = "2", minor = "7", patch = "1") +val pluginVersion: String = pluginVersion(major = "2", minor = "8", patch = "1") val pluginDescriptionFile: String by project val pluginChangeNotesFile: String by project diff --git a/src/IC-242/kotlin/com/amazon/ion/plugin/intellij/formatting/ASTNodeUtils.kt b/src/IC-242/kotlin/com/amazon/ion/plugin/intellij/formatting/ASTNodeUtils.kt new file mode 100644 index 0000000..3531811 --- /dev/null +++ b/src/IC-242/kotlin/com/amazon/ion/plugin/intellij/formatting/ASTNodeUtils.kt @@ -0,0 +1,18 @@ +package com.amazon.ion.plugin.intellij.formatting + +import com.amazon.ion.plugin.intellij.utils.filterWhitespace +import com.intellij.lang.ASTNode +import org.jetbrains.kotlin.idea.base.psi.getLineNumber +import org.jetbrains.kotlin.psi.psiUtil.siblings + +/** + * Determine if a node is on the same line as another node. + */ +fun ASTNode.sameLineAs(another: ASTNode) = + another.psi.getLineNumber(start = true) == this.psi.getLineNumber(start = true) + +/** + * Return the previous sibling of a node if it exists. + */ +fun ASTNode.previousSibling(): ASTNode? = + siblings(forward = false).filterWhitespace().firstOrNull() diff --git a/src/IC-242/kotlin/com/amazon/ion/plugin/intellij/formatting/IonCodeStyleSettingsProvider.kt b/src/IC-242/kotlin/com/amazon/ion/plugin/intellij/formatting/IonCodeStyleSettingsProvider.kt new file mode 100644 index 0000000..eb52d10 --- /dev/null +++ b/src/IC-242/kotlin/com/amazon/ion/plugin/intellij/formatting/IonCodeStyleSettingsProvider.kt @@ -0,0 +1,33 @@ +package com.amazon.ion.plugin.intellij.formatting + +import com.amazon.ion.plugin.intellij.IonLanguage +import com.intellij.application.options.CodeStyleAbstractConfigurable +import com.intellij.application.options.CodeStyleAbstractPanel +import com.intellij.application.options.TabbedLanguageCodeStylePanel +import com.intellij.openapi.options.Configurable +import com.intellij.psi.codeStyle.CodeStyleSettings +import com.intellij.psi.codeStyle.CodeStyleSettingsProvider + +private const val CODE_STYLE_SETTINGS_DISPLAY_NAME = "Ion" + +class IonCodeStyleSettingsProvider : CodeStyleSettingsProvider() { + override fun getConfigurableDisplayName(): String = CODE_STYLE_SETTINGS_DISPLAY_NAME + + override fun createSettingsPage(settings: CodeStyleSettings, modelSettings: CodeStyleSettings): Configurable = + CodeStyleConfigurableConfiguration(settings, modelSettings) +} + +private class CodeStyleConfigurableConfiguration(settings: CodeStyleSettings, modelSettings: CodeStyleSettings) + : CodeStyleAbstractConfigurable(settings, modelSettings, CODE_STYLE_SETTINGS_DISPLAY_NAME) { + + override fun createPanel(settings: CodeStyleSettings): CodeStyleAbstractPanel = IonCodeStyleMainPanel(currentSettings, settings) + override fun getHelpTopic(): String? = null +} + +private class IonCodeStyleMainPanel(currentSettings: CodeStyleSettings, settings: CodeStyleSettings) + : TabbedLanguageCodeStylePanel(IonLanguage.INSTANCE, currentSettings, settings) { + + override fun initTabs(settings: CodeStyleSettings?) { + addIndentOptionsTab(settings) + } +} diff --git a/src/IC-242/kotlin/com/amazon/ion/plugin/intellij/formatting/IonFormattingModelBuilder.kt b/src/IC-242/kotlin/com/amazon/ion/plugin/intellij/formatting/IonFormattingModelBuilder.kt new file mode 100644 index 0000000..51a531b --- /dev/null +++ b/src/IC-242/kotlin/com/amazon/ion/plugin/intellij/formatting/IonFormattingModelBuilder.kt @@ -0,0 +1,34 @@ +package com.amazon.ion.plugin.intellij.formatting + +import com.amazon.ion.plugin.intellij.formatting.blocks.IonBlockOptions +import com.amazon.ion.plugin.intellij.formatting.blocks.RootIonBlock +import com.intellij.formatting.FormattingContext +import com.intellij.formatting.FormattingModel +import com.intellij.formatting.FormattingModelBuilder +import com.intellij.formatting.FormattingModelProvider + +/** + * Creates the block model for an Ion file. + * + * The block model will determine how elements are spaced, indented and aligned. + */ +class IonFormattingModelBuilder : FormattingModelBuilder { + override fun createModel(formattingContext: FormattingContext): FormattingModel { + val element = formattingContext.psiElement + val settings = formattingContext.codeStyleSettings + + val rootBlock = RootIonBlock( + node = element.node, + options = IonBlockOptions( + spaceBuilder = IonCodeBlockSpacingProvider(settings), + codeStyle = settings + ) + ) + + + return FormattingModelProvider.createFormattingModelForPsiFile( + element.containingFile, + rootBlock, settings + ) + } +} diff --git a/src/IC-242/kotlin/com/amazon/ion/plugin/intellij/formatting/blocks/IonSExpressionBlock.kt b/src/IC-242/kotlin/com/amazon/ion/plugin/intellij/formatting/blocks/IonSExpressionBlock.kt new file mode 100644 index 0000000..ac0223e --- /dev/null +++ b/src/IC-242/kotlin/com/amazon/ion/plugin/intellij/formatting/blocks/IonSExpressionBlock.kt @@ -0,0 +1,68 @@ +package com.amazon.ion.plugin.intellij.formatting.blocks + +import com.amazon.ion.plugin.intellij.formatting.previousSibling +import com.amazon.ion.plugin.intellij.formatting.sameLineAs +import com.amazon.ion.plugin.intellij.psi.IonTypes +import com.amazon.ion.plugin.intellij.psi.isOneLiner +import com.amazon.ion.plugin.intellij.utils.elementIsA +import com.intellij.lang.ASTNode +import com.intellij.openapi.diagnostic.debug +import com.intellij.openapi.diagnostic.logger +import com.intellij.psi.tree.IElementType +import org.jetbrains.kotlin.idea.base.psi.getLineNumber + +private val logger = logger() + +class IonSExpressionBlock( + node: ASTNode, + formatting: IonBlockFormattingOptions, + options: IonBlockOptions +) : AbstractIonBlock(node, formatting = formatting, options = options) { + + override val childIndentedTypes: Set = setOf( + IonTypes.SEXPRESSION_ATOM, + IonTypes.COMMENT + ) + + override val childContainerTypes: Set = setOf( + IonTypes.SEXPRESSION_ELEMENTS + ) + + override val containerWrapperTypes: Set = setOf( + IonTypes.LPAREN, + IonTypes.RPAREN + ) + + override fun buildChildBlockFormatting(child: ASTNode): IonBlockFormattingOptions = + buildSpecialCaseChildBlockFormatting(child) ?: + super.buildChildBlockFormatting(child) + + private fun buildSpecialCaseChildBlockFormatting(child: ASTNode): IonBlockFormattingOptions? { + + // Lazy evaluate the previous sibling if needed. + val previous by lazy { child.previousSibling() } + + /** + * Check if we are the first comment within the expression, there is a special comment + * case where we don't want to apply the child alignment to the comment. For example: + * + * (join // special case comment which is inline with operator + * // child comments are inline with inner values + * anotherValue + * ) + */ + if (child elementIsA IonTypes.COMMENT && previous?.elementType == IonTypes.SEXPRESSION_OPERATOR) { + + logger.debug { "Formatting [${child.psi.getLineNumber()}] - Special case inline expression comment line" } + + val comment = child.psi + val expressionOperator = previous!! + + if (comment.isOneLiner() && child.sameLineAs(expressionOperator)) { + return IonBlockFormatting.sameAlignment(this) + } + } + + return null + } +} diff --git a/src/IC-242/kotlin/com/amazon/ion/plugin/intellij/psi/PsiElementExtensions.kt b/src/IC-242/kotlin/com/amazon/ion/plugin/intellij/psi/PsiElementExtensions.kt new file mode 100644 index 0000000..5c36ea7 --- /dev/null +++ b/src/IC-242/kotlin/com/amazon/ion/plugin/intellij/psi/PsiElementExtensions.kt @@ -0,0 +1,11 @@ +package com.amazon.ion.plugin.intellij.psi + +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.idea.base.psi.getLineCount + +/** + * True if the element is all in a single line. + * + * Exists for Backwards Compatibility: <= IC-2020.2 + */ +fun PsiElement.isOneLiner() = getLineCount() == 1