diff --git a/DEVELOPMENT.adoc b/DEVELOPMENT.adoc index 9da6c5ce..6bf44105 100644 --- a/DEVELOPMENT.adoc +++ b/DEVELOPMENT.adoc @@ -92,14 +92,9 @@ Example: `./gradlew test -Djvmdebug=true` == Snippet Test Plugin -There is an IntelliJ plugin meant for development on the Pkl project itself. -This plugin provides a split pane window when viewing snippet tests such as LanguageSnippetTests and FormatterSnippetTests. +There is an IntelliJ plugin meant for development on the Pkl project itself located in https://github.com/apple/pkl-project-commons[pkl-project-commons]. -To install: - -1. Run `./gradlew pkl-internal-intellij-plugin:buildPlugin`. -2. Within IntelliJ, run the action "Install Plugin From Disk...". -3. Select the zip file within `pkl-internal-intellij-plugin/build/distributions`. +See https://github.com/apple/pkl-project-commons?tab=readme-ov-file#internal-intellij-plugin[its readme] for instructions on how to set it up. == Resources diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bae8b5d2..aa3acbe3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -24,8 +24,6 @@ graalVmSha256-linux-aarch64 = "7aa0b9935a80e67f37c6025678393dbd123bb6f2226811dec #noinspection UnusedVersionCatalogEntry graalVmSha256-windows-x64 = "fde83c5ceec2c75560c747ccd9f314f90e4cf5c5287416e67c4ce442e344ca27" ideaExtPlugin = "1.4.1" -intellijPlugin = "2.10.1" -intellij = "2025.2.3" javaPoet = "0.+" javaxInject = "1" jansi = "2.+" @@ -73,8 +71,6 @@ geantyref = { group = "io.leangen.geantyref", name = "geantyref", version.ref = graalCompiler = { group = "org.graalvm.compiler", name = "compiler", version.ref = "graalVm" } graalSdk = { group = "org.graalvm.sdk", name = "graal-sdk", version.ref = "graalVm" } graalJs = { group = "org.graalvm.js", name = "js", version.ref = "graalVm" } -#noinspection UnusedVersionCatalogEntry -intellij = { group = "com.jetbrains.intellij.idea", name = "ideaIC", version.ref = "intellij" } javaPoet = { group = "com.palantir.javapoet", name = "javapoet", version.ref = "javaPoet" } javaxInject = { group = "javax.inject", name = "javax.inject", version.ref = "javaxInject" } jansi = { group = "org.fusesource.jansi", name = "jansi", version.ref = "jansi" } @@ -123,4 +119,3 @@ jmh = { id = "me.champeau.jmh", version.ref = "jmhPlugin" } kotlinxSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } nexusPublish = { id = "io.github.gradle-nexus.publish-plugin", version.ref = "nexusPublishPlugin" } shadow = { id = "com.gradleup.shadow", version.ref = "shadowPlugin" } -intellij = { id = "org.jetbrains.intellij.platform", version.ref = "intellijPlugin" } diff --git a/pkl-internal-intellij-plugin/README.adoc b/pkl-internal-intellij-plugin/README.adoc deleted file mode 100644 index 0796c310..00000000 --- a/pkl-internal-intellij-plugin/README.adoc +++ /dev/null @@ -1,4 +0,0 @@ -Internal IntelliJ plugin for developing the Pkl codebase. - -This plugin is intentionally not published anywhere. -For usage, see the docs in link:../DEVELOPMENT.adoc[DEVELOPMENT.adoc]. diff --git a/pkl-internal-intellij-plugin/gradle.lockfile b/pkl-internal-intellij-plugin/gradle.lockfile deleted file mode 100644 index ffb4db38..00000000 --- a/pkl-internal-intellij-plugin/gradle.lockfile +++ /dev/null @@ -1,63 +0,0 @@ -# This is a Gradle generated file for dependency locking. -# Manual edits can break the build and are not advised. -# This file is expected to be part of source control. -bundledModule:intellij-platform-test-runtime:IC-252.26830.84=intellijPlatformTestRuntimeFixClasspath -bundledModule:intellij.libraries.ai.grazie.spell.gec.engine.local:IC-252.26830.84=compileClasspath,compileOnlyDependenciesMetadata,intellijPlatformBundledPlugins,intellijPlatformDependencies,intellijPlatformTestBundledPlugins,intellijPlatformTestClasspath,intellijPlatformTestDependencies,testCompileClasspath,testCompileOnlyDependenciesMetadata -bundledModule:intellij.libraries.lucene.common:IC-252.26830.84=compileClasspath,compileOnlyDependenciesMetadata,intellijPlatformBundledPlugins,intellijPlatformDependencies,intellijPlatformTestBundledPlugins,intellijPlatformTestClasspath,intellijPlatformTestDependencies,testCompileClasspath,testCompileOnlyDependenciesMetadata -bundledModule:intellij.libraries.microba:IC-252.26830.84=compileClasspath,compileOnlyDependenciesMetadata,intellijPlatformBundledPlugins,intellijPlatformDependencies,intellijPlatformTestBundledPlugins,intellijPlatformTestClasspath,intellijPlatformTestDependencies,testCompileClasspath,testCompileOnlyDependenciesMetadata -bundledModule:intellij.platform.backend:IC-252.26830.84=compileClasspath,compileOnlyDependenciesMetadata,intellijPlatformBundledPlugins,intellijPlatformDependencies,intellijPlatformTestBundledPlugins,intellijPlatformTestClasspath,intellijPlatformTestDependencies,testCompileClasspath,testCompileOnlyDependenciesMetadata -bundledModule:intellij.platform.vcs.impl.lang:IC-252.26830.84=compileClasspath,compileOnlyDependenciesMetadata,intellijPlatformBundledPlugins,intellijPlatformDependencies,intellijPlatformTestBundledPlugins,intellijPlatformTestClasspath,intellijPlatformTestDependencies,testCompileClasspath,testCompileOnlyDependenciesMetadata -bundledModule:intellij.platform.vcs.impl.shared:IC-252.26830.84=compileClasspath,compileOnlyDependenciesMetadata,intellijPlatformBundledPlugins,intellijPlatformDependencies,intellijPlatformTestBundledPlugins,intellijPlatformTestClasspath,intellijPlatformTestDependencies,testCompileClasspath,testCompileOnlyDependenciesMetadata -bundledModule:intellij.platform.vcs.impl:IC-252.26830.84=compileClasspath,compileOnlyDependenciesMetadata,intellijPlatformBundledPlugins,intellijPlatformDependencies,intellijPlatformTestBundledPlugins,intellijPlatformTestClasspath,intellijPlatformTestDependencies,testCompileClasspath,testCompileOnlyDependenciesMetadata -bundledModule:intellij.spellchecker:IC-252.26830.84=compileClasspath,compileOnlyDependenciesMetadata,intellijPlatformBundledPlugins,intellijPlatformDependencies,intellijPlatformTestBundledPlugins,intellijPlatformTestClasspath,intellijPlatformTestDependencies,testCompileClasspath,testCompileOnlyDependenciesMetadata -bundledPlugin:JUnit:IC-252.26830.84=compileClasspath,compileOnlyDependenciesMetadata,intellijPlatformBundledPlugins,intellijPlatformDependencies,intellijPlatformTestBundledPlugins,intellijPlatformTestClasspath,intellijPlatformTestDependencies,testCompileClasspath,testCompileOnlyDependenciesMetadata -bundledPlugin:com.intellij.gradle:IC-252.26830.84=compileClasspath,compileOnlyDependenciesMetadata,intellijPlatformBundledPlugins,intellijPlatformDependencies,intellijPlatformTestBundledPlugins,intellijPlatformTestClasspath,intellijPlatformTestDependencies,testCompileClasspath,testCompileOnlyDependenciesMetadata -bundledPlugin:com.intellij.java:IC-252.26830.84=compileClasspath,compileOnlyDependenciesMetadata,intellijPlatformBundledPlugins,intellijPlatformDependencies,intellijPlatformTestBundledPlugins,intellijPlatformTestClasspath,intellijPlatformTestDependencies,testCompileClasspath,testCompileOnlyDependenciesMetadata -bundledPlugin:org.jetbrains.plugins.gradle:IC-252.26830.84=compileClasspath,compileOnlyDependenciesMetadata,intellijPlatformBundledPlugins,intellijPlatformDependencies,intellijPlatformTestBundledPlugins,intellijPlatformTestClasspath,intellijPlatformTestDependencies,testCompileClasspath,testCompileOnlyDependenciesMetadata -com.github.ben-manes.caffeine:caffeine:2.9.3=swiftExportClasspathResolvable -com.google.errorprone:error_prone_annotations:2.28.0=swiftExportClasspathResolvable -com.jetbrains.intellij.java:java-compiler-ant-tasks:252.26830.84=intellijPlatformJavaCompiler -com.jetbrains.intellij.java:java-compiler-instrumentation-util-java8:252.26830.84=intellijPlatformJavaCompiler -com.jetbrains.intellij.java:java-compiler-instrumentation-util:252.26830.84=intellijPlatformJavaCompiler -com.jetbrains.intellij.java:java-gui-forms-compiler:252.26830.84=intellijPlatformJavaCompiler -com.jetbrains.intellij.java:java-gui-forms-rt:252.26830.84=intellijPlatformJavaCompiler -com.jetbrains.intellij.platform:util-jdom:252.26830.84=intellijPlatformJavaCompiler -com.jgoodies:forms:1.1-preview=intellijPlatformJavaCompiler -idea:ideaIC:2025.2.3=compileClasspath,compileOnlyDependenciesMetadata,intellijPlatformClasspath,intellijPlatformDependency,intellijPlatformDependencyArchive,intellijPlatformTestClasspath,testCompileClasspath,testCompileOnlyDependenciesMetadata -io.github.java-diff-utils:java-diff-utils:4.12=kotlinInternalAbiValidation -io.opentelemetry:opentelemetry-api:1.41.0=swiftExportClasspathResolvable -io.opentelemetry:opentelemetry-context:1.41.0=swiftExportClasspathResolvable -jaxen:jaxen:1.2.0=intellijPlatformJavaCompiler -org.bouncycastle:bcpg-jdk18on:1.80=kotlinBouncyCastleConfiguration -org.bouncycastle:bcpkix-jdk18on:1.80=kotlinBouncyCastleConfiguration -org.bouncycastle:bcprov-jdk18on:1.80=kotlinBouncyCastleConfiguration -org.bouncycastle:bcutil-jdk18on:1.80=kotlinBouncyCastleConfiguration -org.checkerframework:checker-qual:3.43.0=swiftExportClasspathResolvable -org.jetbrains.intellij.deps:asm-all:9.6.1=intellijPlatformJavaCompiler -org.jetbrains.intellij.plugins:verifier-cli:1.402=intellijPluginVerifier -org.jetbrains.kotlin:abi-tools-api:2.2.21=kotlinInternalAbiValidation -org.jetbrains.kotlin:abi-tools:2.2.21=kotlinInternalAbiValidation -org.jetbrains.kotlin:kotlin-build-tools-api:2.2.21=kotlinBuildToolsApiClasspath -org.jetbrains.kotlin:kotlin-build-tools-impl:2.2.21=kotlinBuildToolsApiClasspath -org.jetbrains.kotlin:kotlin-compiler-embeddable:2.2.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable -org.jetbrains.kotlin:kotlin-compiler-runner:2.2.21=kotlinBuildToolsApiClasspath -org.jetbrains.kotlin:kotlin-daemon-client:2.2.21=kotlinBuildToolsApiClasspath -org.jetbrains.kotlin:kotlin-daemon-embeddable:2.2.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable -org.jetbrains.kotlin:kotlin-klib-commonizer-embeddable:2.2.21=kotlinKlibCommonizerClasspath -org.jetbrains.kotlin:kotlin-metadata-jvm:2.2.21=kotlinInternalAbiValidation -org.jetbrains.kotlin:kotlin-reflect:2.2.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable -org.jetbrains.kotlin:kotlin-script-runtime:2.2.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable -org.jetbrains.kotlin:kotlin-scripting-common:2.2.21=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest -org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:2.2.21=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest -org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:2.2.21=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest -org.jetbrains.kotlin:kotlin-scripting-jvm:2.2.21=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest -org.jetbrains.kotlin:kotlin-stdlib:2.2.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable -org.jetbrains.kotlin:swift-export-embeddable:2.2.21=swiftExportClasspathResolvable -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.0=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable -org.jetbrains.kotlinx:kotlinx-serialization-bom:1.7.3=swiftExportClasspathResolvable -org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.7.3=swiftExportClasspathResolvable -org.jetbrains.kotlinx:kotlinx-serialization-core:1.7.3=swiftExportClasspathResolvable -org.jetbrains:annotations:13.0=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinInternalAbiValidation,kotlinKlibCommonizerClasspath,swiftExportClasspathResolvable -org.jetbrains:annotations:26.0.2=intellijPlatformJavaCompiler -org.jetbrains:marketplace-zip-signer:0.1.43=marketplaceZipSigner -empty=annotationProcessor,apiDependenciesMetadata,implementationDependenciesMetadata,intellijPlatformBundledModules,intellijPlatformComposedJar,intellijPlatformLocal,intellijPlatformPlugin,intellijPlatformPluginComposedModule,intellijPlatformPluginDependency,intellijPlatformPluginLocal,intellijPlatformPluginModule,intellijPlatformRuntimeClasspath,intellijPlatformTestBundledModules,intellijPlatformTestPlugin,intellijPlatformTestPluginDependency,intellijPlatformTestPluginLocal,intellijPlatformTestRuntimeClasspath,intellijPluginVerifierIdes,intellijPluginVerifierIdesDependency,intellijPluginVerifierIdesLocalInstance,intransitiveDependenciesMetadata,jetbrainsRuntime,jetbrainsRuntimeDependency,jetbrainsRuntimeLocalInstance,kotlinCompilerPluginClasspath,kotlinNativeCompilerPluginClasspath,kotlinScriptDefExtensions,runtimeClasspath,testAnnotationProcessor,testApiDependenciesMetadata,testImplementationDependenciesMetadata,testIntransitiveDependenciesMetadata,testKotlinScriptDefExtensions,testRuntimeClasspath diff --git a/pkl-internal-intellij-plugin/pkl-internal-intellij-plugin.gradle.kts b/pkl-internal-intellij-plugin/pkl-internal-intellij-plugin.gradle.kts deleted file mode 100644 index 1aaadd9c..00000000 --- a/pkl-internal-intellij-plugin/pkl-internal-intellij-plugin.gradle.kts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved. - * - * 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. - */ -plugins { - pklAllProjects - kotlin("jvm") - alias(libs.plugins.intellij) -} - -repositories { - mavenCentral() - intellijPlatform { defaultRepositories() } -} - -dependencies { - intellijPlatform { - create("IC", libs.versions.intellij.get()) - bundledPlugin("com.intellij.java") - bundledPlugin("org.jetbrains.plugins.gradle") - bundledPlugin("JUnit") - } -} - -spotless { - kotlin { - ktfmt(libs.versions.ktfmt.get()).googleStyle() - target("src/*/kotlin/**/*.kt") - licenseHeaderFile(rootProject.file("buildSrc/src/main/resources/license-header.star-block.txt")) - } -} diff --git a/pkl-internal-intellij-plugin/src/main/kotlin/org/pkl/internal/intellij/SnippetTestEditorProvider.kt b/pkl-internal-intellij-plugin/src/main/kotlin/org/pkl/internal/intellij/SnippetTestEditorProvider.kt deleted file mode 100644 index 0eda994c..00000000 --- a/pkl-internal-intellij-plugin/src/main/kotlin/org/pkl/internal/intellij/SnippetTestEditorProvider.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved. - * - * 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. - */ -package org.pkl.internal.intellij - -import com.intellij.openapi.fileEditor.* -import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider -import com.intellij.openapi.project.DumbAware -import com.intellij.openapi.project.Project -import com.intellij.openapi.vfs.VirtualFile - -class SnippetTestEditorProvider : FileEditorProvider, DumbAware { - override fun accept(project: Project, file: VirtualFile): Boolean = file.isSnippetTestInputFile() - - override fun createEditor(project: Project, file: VirtualFile): FileEditor { - val textEditorProvider = TextEditorProvider.getInstance() - val outputFile = findOutputFile(file) - - val inputEditor = textEditorProvider.createEditor(project, file) as TextEditor - val outputEditor = - outputFile?.let { textEditorProvider.createEditor(project, it) as TextEditor } - - return SnippetTestSplitEditor(inputEditor, outputEditor) - } - - override fun getEditorTypeId(): String = "snippet-test-split-editor" - - override fun getPolicy(): FileEditorPolicy = FileEditorPolicy.HIDE_DEFAULT_EDITOR -} diff --git a/pkl-internal-intellij-plugin/src/main/kotlin/org/pkl/internal/intellij/SnippetTestSplitEditor.kt b/pkl-internal-intellij-plugin/src/main/kotlin/org/pkl/internal/intellij/SnippetTestSplitEditor.kt deleted file mode 100644 index 0266d275..00000000 --- a/pkl-internal-intellij-plugin/src/main/kotlin/org/pkl/internal/intellij/SnippetTestSplitEditor.kt +++ /dev/null @@ -1,449 +0,0 @@ -/* - * Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved. - * - * 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. - */ -package org.pkl.internal.intellij - -import com.intellij.execution.ExecutionListener -import com.intellij.execution.ExecutionManager -import com.intellij.execution.Executor -import com.intellij.execution.ProgramRunnerUtil -import com.intellij.execution.RunManager -import com.intellij.execution.executors.DefaultDebugExecutor -import com.intellij.execution.executors.DefaultRunExecutor -import com.intellij.execution.junit.JUnitConfiguration -import com.intellij.execution.junit.JUnitConfigurationType -import com.intellij.execution.process.ProcessHandler -import com.intellij.execution.runners.ExecutionEnvironment -import com.intellij.icons.AllIcons -import com.intellij.openapi.actionSystem.* -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.fileEditor.FileEditor -import com.intellij.openapi.fileEditor.FileEditorLocation -import com.intellij.openapi.fileEditor.FileEditorState -import com.intellij.openapi.fileEditor.FileEditorStateLevel -import com.intellij.openapi.fileEditor.TextEditor -import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider -import com.intellij.openapi.module.ModuleUtil -import com.intellij.openapi.project.Project -import com.intellij.openapi.util.UserDataHolderBase -import com.intellij.openapi.vfs.VirtualFile -import com.intellij.openapi.vfs.VirtualFileManager -import com.intellij.psi.JavaPsiFacade -import com.intellij.psi.PsiClass -import com.intellij.psi.search.GlobalSearchScope -import com.intellij.ui.components.JBPanel -import java.awt.BorderLayout -import java.beans.PropertyChangeListener -import javax.swing.JComponent -import javax.swing.JSplitPane - -class SnippetTestSplitEditor( - private val inputEditor: TextEditor, - private var outputEditor: TextEditor?, -) : UserDataHolderBase(), FileEditor { - - private var currentViewMode = if (outputEditor != null) ViewMode.SPLIT else ViewMode.INPUT_ONLY - - private val splitPane: JSplitPane = - JSplitPane(JSplitPane.HORIZONTAL_SPLIT, inputEditor.component, outputEditor?.component).apply { - resizeWeight = 0.5 - } - - private val mainPanel = - JBPanel>(BorderLayout()).apply { - add(createToolbar(), BorderLayout.NORTH) - when (currentViewMode) { - ViewMode.INPUT_ONLY -> add(inputEditor.component, BorderLayout.CENTER) - ViewMode.SPLIT -> add(splitPane, BorderLayout.CENTER) - } - } - - private fun createToolbar(): JComponent { - val actionGroup = - DefaultActionGroup().apply { - add(RunTestAction()) - add(DebugTestAction()) - add(RunAllTestsAction()) - add(OverwriteSnippetAction()) - addSeparator() - add(ShowInputOnlyAction()) - add(ShowSplitAction()) - } - - val toolbar = - ActionManager.getInstance().createActionToolbar("SnippetTestEditor", actionGroup, true) - toolbar.targetComponent = mainPanel - return toolbar.component - } - - private fun setViewMode(mode: ViewMode) { - if (currentViewMode == mode || outputEditor == null) return - currentViewMode = mode - - // Remove the current center component - val layout = mainPanel.layout as BorderLayout - layout.getLayoutComponent(BorderLayout.CENTER)?.let { mainPanel.remove(it) } - - when (mode) { - ViewMode.INPUT_ONLY -> { - mainPanel.add(inputEditor.component, BorderLayout.CENTER) - } - ViewMode.SPLIT -> { - // Re-add components to splitPane in case they were removed - splitPane.leftComponent = inputEditor.component - splitPane.rightComponent = outputEditor!!.component - mainPanel.add(splitPane, BorderLayout.CENTER) - } - } - - mainPanel.revalidate() - mainPanel.repaint() - } - - override fun getComponent(): JComponent = mainPanel - - override fun getPreferredFocusedComponent(): JComponent? = inputEditor.preferredFocusedComponent - - override fun getName(): String = "Snippet Test" - - override fun getFile(): VirtualFile? = inputEditor.file - - override fun setState(state: FileEditorState) { - if (state is SnippetTestSplitEditorState) { - inputEditor.setState(state.inputState) - if (outputEditor != null && state.outputState != null) { - outputEditor!!.setState(state.outputState) - } - } - } - - override fun getState(level: FileEditorStateLevel): FileEditorState { - return SnippetTestSplitEditorState(inputEditor.getState(level), outputEditor?.getState(level)) - } - - override fun isModified(): Boolean = inputEditor.isModified || outputEditor?.isModified ?: false - - override fun isValid(): Boolean = inputEditor.isValid && outputEditor?.isValid ?: true - - override fun addPropertyChangeListener(listener: PropertyChangeListener) { - inputEditor.addPropertyChangeListener(listener) - outputEditor?.addPropertyChangeListener(listener) - } - - override fun removePropertyChangeListener(listener: PropertyChangeListener) { - inputEditor.removePropertyChangeListener(listener) - outputEditor?.removePropertyChangeListener(listener) - } - - override fun getCurrentLocation(): FileEditorLocation? = inputEditor.currentLocation - - override fun dispose() { - inputEditor.dispose() - outputEditor?.dispose() - } - - /** - * Refreshes the output editor by reloading the output file from disk. This is useful after - * running tests, as the output file may have been created or changed (e.g., from .pcf to .err). - */ - private fun refreshOutputEditor(project: Project) { - val inputFile = inputEditor.file ?: return - - // Refresh the input file's parent to pick up any new output files - ApplicationManager.getApplication().invokeLater { - VirtualFileManager.getInstance().asyncRefresh { - val currentOutputFile = findOutputFile(inputFile) - val editorOutputFile = outputEditor?.file - when { - currentOutputFile != null && currentOutputFile != editorOutputFile -> { - // No output file exists; set split mode. - if (editorOutputFile == null) { - replaceOutputEditorAndSetSplitMode(project, currentOutputFile) - } else { - // The output file has changed (e.g., .pcf to .err or vice versa), or got created. - // We need to replace the output editor with a new one for the new file - replaceOutputEditor(project, currentOutputFile) - } - } - else -> currentOutputFile?.refresh(true, false) - } - } - } - } - - /** - * Replaces the current output editor with a new one for the specified file. This is necessary - * when the output file type changes (e.g., from .pcf to .err). - */ - private fun replaceOutputEditorAndSetSplitMode(project: Project, newOutputFile: VirtualFile) { - ApplicationManager.getApplication().invokeLater { - val textEditorProvider = TextEditorProvider.getInstance() - val newEditor = textEditorProvider.createEditor(project, newOutputFile) as TextEditor - - // Dispose the old editor - outputEditor?.dispose() - - // Update the reference to the new editor - outputEditor = newEditor - setViewMode(ViewMode.SPLIT) - } - } - - /** - * Replaces the current output editor with a new one for the specified file. This is necessary - * when the output file type changes (e.g., from .pcf to .err). - */ - private fun replaceOutputEditor(project: Project, newOutputFile: VirtualFile) { - ApplicationManager.getApplication().invokeLater { - val textEditorProvider = TextEditorProvider.getInstance() - val newEditor = textEditorProvider.createEditor(project, newOutputFile) as TextEditor - - // Dispose the old editor - outputEditor?.dispose() - - // Update the reference to the new editor - outputEditor = newEditor - - // Update the split pane with the new editor - when (currentViewMode) { - ViewMode.SPLIT -> { - splitPane.rightComponent = newEditor.component - splitPane.revalidate() - splitPane.repaint() - } - ViewMode.INPUT_ONLY -> { - // Nothing to do, output is not visible - } - } - } - } - - private enum class ViewMode { - INPUT_ONLY, - SPLIT, - } - - private inner class ShowInputOnlyAction : - ToggleAction("Show Input Only", "Show only the input file", AllIcons.General.LayoutEditorOnly) { - override fun isSelected(e: AnActionEvent): Boolean = currentViewMode == ViewMode.INPUT_ONLY - - override fun setSelected(e: AnActionEvent, state: Boolean) { - if (state) setViewMode(ViewMode.INPUT_ONLY) - } - } - - private inner class ShowSplitAction : - ToggleAction( - "Show Split", - "Show input and output side by side", - AllIcons.General.LayoutEditorPreview, - ) { - override fun isSelected(e: AnActionEvent): Boolean = currentViewMode == ViewMode.SPLIT - - override fun setSelected(e: AnActionEvent, state: Boolean) { - if (state) setViewMode(ViewMode.SPLIT) - } - } - - private inner class RunTestAction : - AnAction("Run Test", "Run the snippet test", AllIcons.Actions.Execute) { - override fun actionPerformed(e: AnActionEvent) { - val project = e.project ?: return - executeTest(project, DefaultRunExecutor.getRunExecutorInstance()) - } - } - - private inner class RunAllTestsAction : - AnAction("Run All Tests", "Run all snippet tests", AllIcons.Actions.RunAll) { - override fun actionPerformed(e: AnActionEvent) { - val project = e.project ?: return - executeAllTests(project, DefaultRunExecutor.getRunExecutorInstance()) - } - } - - private inner class DebugTestAction : - AnAction("Debug Test", "Debug the snippet test", AllIcons.Actions.StartDebugger) { - override fun actionPerformed(e: AnActionEvent) { - val project = e.project ?: return - executeTest(project, DefaultDebugExecutor.getDebugExecutorInstance()) - } - } - - private inner class OverwriteSnippetAction : - AnAction( - "Overwrite Snippet", - "Run test and regenerate expected output", - AllIcons.Actions.RerunAutomatically, - ) { - override fun actionPerformed(e: AnActionEvent) { - val project = e.project ?: return - - executeTest( - project, - DefaultRunExecutor.getRunExecutorInstance(), - mapOf("OVERWRITE_SNIPPETS" to "1"), - ) - } - } - - /** - * Builds a JUnit UniqueID selector for the snippet test. E.g. - * - * ``` - * [engine:LanguageSnippetTestsEngine]/[inputDirNode:lambdas]/[inputFileNode:lambdaStackTrace2.pkl] - * ``` - */ - private fun buildUniqueId(file: VirtualFile): String? { - val path = file.path - // Pattern: .../LanguageSnippetTests/input/lambdas/lambdaStackTrace2.pkl - val pattern = Regex(".*/([^/]+)/src/test/files/(\\w+)/input/(.+)$") - val match = pattern.find(path) ?: return null - - val testType = match.groupValues[2] // e.g., "LanguageSnippetTests" - val relativePath = match.groupValues[3] // e.g., "lambdas/lambdaStackTrace2.pkl" - - // Extract directory and filename - val parts = relativePath.split("/") - val fileName = parts.last() - val engineName = testType + "Engine" - val uniqueId = buildString { - append("[engine:$engineName]") - if (parts.size > 1) { - for (dir in parts.dropLast(1)) { - append("/[inputDirNode:$dir]") - } - } - append("/[inputFileNode:$fileName]") - } - - return uniqueId - } - - private fun getTestClass(project: Project, file: VirtualFile): PsiClass { - val path = file.path - // Pattern: .../LanguageSnippetTests/input/lambdas/lambdaStackTrace2.pkl - val pattern = Regex(".*/([^/]+)/src/test/files/(\\w+)/input/(.+)$") - val match = pattern.find(path)!! - val folder = match.groupValues[2] - val className = - when (folder) { - "LanguageSnippetTests" -> "org.pkl.core.LanguageSnippetTests" - "FormatterSnippetTests" -> "org.pkl.formatter.FormatterSnippetTests" - // legacy; doesn't exist on main branch - "SnippetTests" -> "org.pkl.server.SnippetTests" - else -> throw IllegalStateException("") - } - return JavaPsiFacade.getInstance(project) - .findClass(className, GlobalSearchScope.allScope(project))!! - } - - private fun executeAllTests(project: Project, executor: Executor) { - val file = inputEditor.file ?: return - - val path = file.path - // Pattern: .../LanguageSnippetTests/input/lambdas/lambdaStackTrace2.pkl - val pattern = Regex(".*/([^/]+)/src/test/files/(\\w+)/input/(.+)$") - val match = pattern.find(path) ?: return - - val testType = match.groupValues[2] // e.g., "LanguageSnippetTests" - executeTest(project, executor, testType) { data -> - data.TEST_OBJECT = JUnitConfiguration.TEST_CLASS - data.setMainClass(getTestClass(project, file)) - } - } - - private fun executeTest( - project: Project, - executor: Executor, - envVars: Map = emptyMap(), - ) { - val file = inputEditor.file ?: return - val uniqueId = buildUniqueId(file) ?: return - executeTest(project, executor, file.name) { data -> - data.TEST_OBJECT = JUnitConfiguration.TEST_UNIQUE_ID - data.setUniqueIds(uniqueId) - - if (envVars.isNotEmpty()) { - data.envs = envVars.toMutableMap() - data.PASS_PARENT_ENVS = true - } - } - } - - private fun executeTest( - project: Project, - executor: Executor, - title: String, - configure: (JUnitConfiguration.Data) -> Unit, - ) { - val file = inputEditor.file ?: return - val module = ModuleUtil.findModuleForFile(file, project) ?: return - - val runManager = RunManager.getInstance(project) - val configurationType = JUnitConfigurationType.getInstance() - val configurationFactory = configurationType.configurationFactories.first() - - val settings = runManager.createConfiguration(title, configurationFactory) - - val configuration = settings.configuration as? JUnitConfiguration ?: return - configure(configuration.persistentData) - - configuration.setModule(module) - - // Add the configuration to the RunManager so it appears in recent configurations - runManager.addConfiguration(settings) - runManager.selectedConfiguration = settings - - // Add listener to refresh output editor after test completes - val messageBus = project.messageBus.connect() - messageBus.subscribe( - ExecutionManager.EXECUTION_TOPIC, - object : ExecutionListener { - override fun processTerminated( - executorId: String, - env: ExecutionEnvironment, - handler: ProcessHandler, - exitCode: Int, - ) { - // Check if this is our test run - if (env.runProfile == configuration) { - // Refresh the output editor after the test completes - refreshOutputEditor(project) - // Disconnect the listener after use - messageBus.disconnect() - } - } - }, - ) - - ProgramRunnerUtil.executeConfiguration(settings, executor) - } -} - -data class SnippetTestSplitEditorState( - val inputState: FileEditorState, - val outputState: FileEditorState?, -) : FileEditorState { - override fun canBeMergedWith(otherState: FileEditorState, level: FileEditorStateLevel): Boolean { - val other = otherState as? SnippetTestSplitEditorState ?: return false - if (!inputState.canBeMergedWith(other.inputState, level)) return false - val otherState = other.outputState - return when { - outputState == null && otherState == null -> true - outputState != null && otherState != null -> outputState.canBeMergedWith(otherState, level) - else -> false - } - } -} diff --git a/pkl-internal-intellij-plugin/src/main/kotlin/org/pkl/internal/intellij/util.kt b/pkl-internal-intellij-plugin/src/main/kotlin/org/pkl/internal/intellij/util.kt deleted file mode 100644 index 86c15724..00000000 --- a/pkl-internal-intellij-plugin/src/main/kotlin/org/pkl/internal/intellij/util.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved. - * - * 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. - */ -package org.pkl.internal.intellij - -import com.intellij.openapi.vfs.VirtualFile -import com.intellij.openapi.vfs.VirtualFileManager - -internal fun VirtualFile.isSnippetTestInputFile(): Boolean { - return path.contains("/src/test/files/") && path.contains("/input/") && extension == "pkl" -} - -private val hiddenExtensionRegex = Regex(".*[.]([^.]*)[.]pkl") - -private fun possibleOutputPaths(testType: String, relativePath: String): String? { - return when (testType) { - "LanguageSnippetTests" -> - if (relativePath.matches(hiddenExtensionRegex)) relativePath.dropLast(4) - else relativePath.dropLast(3) + "pcf" - "FormatterSnippetTests" -> relativePath - "SnippetTests" -> relativePath.replaceAfterLast('.', "yaml") - else -> null - } -} - -internal fun findOutputFile(inputFile: VirtualFile): VirtualFile? { - val path = inputFile.path - val inputPattern = Regex(".*/src/test/files/(\\w+)/input/(.+)$") - val match = inputPattern.find(path) ?: return null - - val testType = match.groupValues[1] - val relativePath = match.groupValues[2] - val relativeOutputPath = possibleOutputPaths(testType, relativePath) ?: return null - val outputPath = path.replace("/input/$relativePath", "/output/$relativeOutputPath") - val fileManager = VirtualFileManager.getInstance() - return fileManager.findFileByUrl("file://$outputPath") - ?: fileManager.findFileByUrl("file://${outputPath.replaceAfterLast('.', "err")}") -} diff --git a/pkl-internal-intellij-plugin/src/main/resources/META-INF/plugin.xml b/pkl-internal-intellij-plugin/src/main/resources/META-INF/plugin.xml deleted file mode 100644 index 7ac5b3cb..00000000 --- a/pkl-internal-intellij-plugin/src/main/resources/META-INF/plugin.xml +++ /dev/null @@ -1,19 +0,0 @@ - - org.pkl.intellij.snippet-test-helper - Pkl Snippet Test Helper - Apple Inc. and the Pkl project authors - - - - com.intellij.modules.platform - com.intellij.java - org.jetbrains.plugins.gradle - - - - - diff --git a/settings.gradle.kts b/settings.gradle.kts index 22332f07..d883f0cf 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -47,8 +47,6 @@ include("pkl-formatter") include("pkl-gradle") -include("pkl-internal-intellij-plugin") - include("pkl-parser") include("pkl-server")