Remove intellij plugin (#1510)

This plugin is being moved to pkl-project-commons. 

Closes #1491

See https://github.com/apple/pkl-project-commons/pull/75
This commit is contained in:
Daniel Chao
2026-04-08 13:46:23 -07:00
committed by GitHub
parent aca5a32f8e
commit ff6f7223d3
10 changed files with 2 additions and 682 deletions

View File

@@ -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

View File

@@ -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" }

View File

@@ -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].

View File

@@ -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

View File

@@ -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"))
}
}

View File

@@ -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
}

View File

@@ -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<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<String, String> = 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
}
}
}

View File

@@ -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")}")
}

View File

@@ -1,19 +0,0 @@
<idea-plugin>
<id>org.pkl.intellij.snippet-test-helper</id>
<name>Pkl Snippet Test Helper</name>
<vendor>Apple Inc. and the Pkl project authors</vendor>
<description><![CDATA[
Automatically opens snippet test output files when input files are opened.
This is an internal plugin that is meant for development on the Pkl project itself.
]]></description>
<depends>com.intellij.modules.platform</depends>
<depends>com.intellij.java</depends>
<depends>org.jetbrains.plugins.gradle</depends>
<extensions defaultExtensionNs="com.intellij">
<fileEditorProvider implementation="org.pkl.internal.intellij.SnippetTestEditorProvider"/>
</extensions>
</idea-plugin>

View File

@@ -47,8 +47,6 @@ include("pkl-formatter")
include("pkl-gradle")
include("pkl-internal-intellij-plugin")
include("pkl-parser")
include("pkl-server")