From 34793e63c7d26610ae73e055150d23df9e579ab9 Mon Sep 17 00:00:00 2001 From: "Pavel V. Talanov" Date: Thu, 19 Nov 2015 19:38:18 +0300 Subject: [PATCH] Allow to run kts files as scripts from ide Introduce KotlinStandaloneScriptRunConfiguration: configuration, type, producer, settings editor --- idea/src/META-INF/plugin.xml | 2 + .../KotlinStandaloneScriptRunConfiguration.kt | 195 ++++++++++++++++++ ...tandaloneScriptRunConfigurationEditor.form | 39 ++++ ...tandaloneScriptRunConfigurationEditor.java | 105 ++++++++++ ...tandaloneScriptRunConfigurationProducer.kt | 55 +++++ ...linStandaloneScriptRunConfigurationType.kt | 43 ++++ 6 files changed, 439 insertions(+) create mode 100644 idea/src/org/jetbrains/kotlin/idea/run/script/standalone/KotlinStandaloneScriptRunConfiguration.kt create mode 100644 idea/src/org/jetbrains/kotlin/idea/run/script/standalone/KotlinStandaloneScriptRunConfigurationEditor.form create mode 100644 idea/src/org/jetbrains/kotlin/idea/run/script/standalone/KotlinStandaloneScriptRunConfigurationEditor.java create mode 100644 idea/src/org/jetbrains/kotlin/idea/run/script/standalone/KotlinStandaloneScriptRunConfigurationProducer.kt create mode 100644 idea/src/org/jetbrains/kotlin/idea/run/script/standalone/KotlinStandaloneScriptRunConfigurationType.kt diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 30629652201..02c0e62bcb2 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -456,9 +456,11 @@ + + diff --git a/idea/src/org/jetbrains/kotlin/idea/run/script/standalone/KotlinStandaloneScriptRunConfiguration.kt b/idea/src/org/jetbrains/kotlin/idea/run/script/standalone/KotlinStandaloneScriptRunConfiguration.kt new file mode 100644 index 00000000000..d7c3bf5ad4d --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/run/script/standalone/KotlinStandaloneScriptRunConfiguration.kt @@ -0,0 +1,195 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * 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. + */ + +package org.jetbrains.kotlin.idea.run.script.standalone + +import com.intellij.execution.* +import com.intellij.execution.application.BaseJavaApplicationCommandLineState +import com.intellij.execution.configuration.EnvironmentVariablesComponent +import com.intellij.execution.configurations.* +import com.intellij.execution.runners.ExecutionEnvironment +import com.intellij.execution.util.JavaParametersUtil +import com.intellij.execution.util.ProgramParametersUtil +import com.intellij.openapi.components.PathMacroManager +import com.intellij.openapi.options.SettingsEditor +import com.intellij.openapi.options.SettingsEditorGroup +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.DefaultJDOMExternalizer +import com.intellij.openapi.vfs.LocalFileSystem +import com.intellij.psi.PsiElement +import com.intellij.refactoring.listeners.RefactoringElementAdapter +import com.intellij.refactoring.listeners.RefactoringElementListener +import org.jdom.Element +import org.jetbrains.kotlin.idea.run.script.standalone.KotlinStandaloneScriptRunConfigurationProducer.Companion.pathFromPsiElement +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.utils.PathUtil +import java.io.File +import java.util.* + +class KotlinStandaloneScriptRunConfiguration( + project: Project, + factory: ConfigurationFactory, + name: String? +) : LocatableConfigurationBase(project, factory, name), CommonJavaRunConfigurationParameters, RefactoringListenerProvider { + @JvmField + var filePath: String? = null + @JvmField + var vmParameters: String? = null + @JvmField + var alternativeJrePath: String? = null + @JvmField + var programParameters: String? = null + @JvmField + var envs: MutableMap = LinkedHashMap() + @JvmField + var passParentEnvs: Boolean = true + @JvmField + var workingDirectory: String? = null + @JvmField + var isAlternativeJrePathEnabled: Boolean = false + + override fun getVMParameters() = vmParameters + override fun setVMParameters(value: String?) { + vmParameters = value + } + + override fun getAlternativeJrePath() = alternativeJrePath + override fun setAlternativeJrePath(path: String?) { + alternativeJrePath = path + } + + override fun getProgramParameters() = programParameters + override fun setProgramParameters(value: String?) { + programParameters = value + } + + override fun getEnvs() = envs + override fun setEnvs(envs: MutableMap) { + this.envs = envs + } + + override fun getWorkingDirectory() = workingDirectory + override fun setWorkingDirectory(value: String?) { + workingDirectory = value + } + + override fun isPassParentEnvs() = passParentEnvs + override fun setPassParentEnvs(passParentEnvs: Boolean) { + this.passParentEnvs = passParentEnvs + } + + override fun isAlternativeJrePathEnabled() = isAlternativeJrePathEnabled + override fun setAlternativeJrePathEnabled(enabled: Boolean) { + isAlternativeJrePathEnabled = enabled + } + + override fun getState(executor: Executor, environment: ExecutionEnvironment): RunProfileState = ScriptCommandLineState(environment, this) + + override fun suggestedName() = filePath?.let { File(it).nameWithoutExtension } + + override fun getConfigurationEditor(): SettingsEditor { + val group = SettingsEditorGroup() + group.addEditor(ExecutionBundle.message("run.configuration.configuration.tab.title"), KotlinStandaloneScriptRunConfigurationEditor(project)) + JavaRunConfigurationExtensionManager.getInstance().appendEditors(this, group) + return group + } + + override fun writeExternal(element: Element) { + super.writeExternal(element) + JavaRunConfigurationExtensionManager.getInstance().writeExternal(this, element) + DefaultJDOMExternalizer.writeExternal(this, element) + EnvironmentVariablesComponent.writeExternal(element, getEnvs()) + PathMacroManager.getInstance(project).collapsePathsRecursively(element) + } + + override fun readExternal(element: Element) { + PathMacroManager.getInstance(project).expandPaths(element) + super.readExternal(element) + JavaRunConfigurationExtensionManager.getInstance().readExternal(this, element) + DefaultJDOMExternalizer.readExternal(this, element) + EnvironmentVariablesComponent.readExternal(element, getEnvs()) + } + + override fun checkConfiguration() { + JavaParametersUtil.checkAlternativeJRE(this) + ProgramParametersUtil.checkWorkingDirectoryExist(this, project, null) + JavaRunConfigurationExtensionManager.checkConfigurationIsValid(this) + + if (filePath.isNullOrEmpty()) { + runtimeConfigurationWarning("File was not specified") + } + val virtualFile = LocalFileSystem.getInstance().findFileByIoFile(File(filePath)) + if (virtualFile == null || virtualFile.isDirectory) { + runtimeConfigurationWarning("Could not find script file: $filePath") + } + } + + private fun runtimeConfigurationWarning(message: String): Nothing { + throw RuntimeConfigurationWarning(message) + } + + // NOTE: this is needed for coverage + override fun getRunClass() = null + + override fun getPackage() = null + + override fun getRefactoringElementListener(element: PsiElement): RefactoringElementListener? { + val file = element as? KtFile ?: return null + val pathFromElement = pathFromPsiElement(file) ?: return null + + if (filePath != pathFromElement) { + return null + } + + return object : RefactoringElementAdapter() { + override fun undoElementMovedOrRenamed(newElement: PsiElement, oldQualifiedName: String) { + filePath = pathFromPsiElement(newElement) + } + + override fun elementRenamedOrMoved(newElement: PsiElement) { + filePath = pathFromPsiElement(newElement) + } + } + } + +} + +private class ScriptCommandLineState(environment: ExecutionEnvironment, configuration: KotlinStandaloneScriptRunConfiguration) : + BaseJavaApplicationCommandLineState(environment, configuration) { + + override fun createJavaParameters(): JavaParameters? { + val params = commonParameters() + + val kotlinPaths = PathUtil.getKotlinPathsForIdeaPlugin() + listOf(kotlinPaths.compilerPath, kotlinPaths.reflectPath, kotlinPaths.runtimePath) + .map { it.absolutePath } + .forEach { dependencyJar -> params.classPath.add(dependencyJar) } + + params.mainClass = "org.jetbrains.kotlin.cli.jvm.K2JVMCompiler" + params.programParametersList.prepend(configuration.filePath ?: throw CantRunException("Script file was not specified")) + params.programParametersList.prepend("-script") + + return params + } + + private fun commonParameters(): JavaParameters { + val params = JavaParameters() + setupJavaParameters(params) + val jreHome = if (configuration.isAlternativeJrePathEnabled) myConfiguration.alternativeJrePath else null + JavaParametersUtil.configureProject(environment.project, params, JavaParameters.JDK_ONLY, jreHome) + return params + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/run/script/standalone/KotlinStandaloneScriptRunConfigurationEditor.form b/idea/src/org/jetbrains/kotlin/idea/run/script/standalone/KotlinStandaloneScriptRunConfigurationEditor.form new file mode 100644 index 00000000000..a2a3853fa0e --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/run/script/standalone/KotlinStandaloneScriptRunConfigurationEditor.form @@ -0,0 +1,39 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/idea/src/org/jetbrains/kotlin/idea/run/script/standalone/KotlinStandaloneScriptRunConfigurationEditor.java b/idea/src/org/jetbrains/kotlin/idea/run/script/standalone/KotlinStandaloneScriptRunConfigurationEditor.java new file mode 100644 index 00000000000..5c358c2be6f --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/run/script/standalone/KotlinStandaloneScriptRunConfigurationEditor.java @@ -0,0 +1,105 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * 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. + */ + +package org.jetbrains.kotlin.idea.run.script.standalone; + +import com.intellij.execution.ui.AlternativeJREPanel; +import com.intellij.execution.ui.CommonJavaParametersPanel; +import com.intellij.openapi.fileChooser.FileChooserDescriptor; +import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; +import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.options.SettingsEditor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.openapi.ui.LabeledComponent; +import com.intellij.openapi.ui.TextComponentAccessor; +import com.intellij.openapi.ui.TextFieldWithBrowseButton; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.ui.PanelWithAnchor; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.kotlin.parsing.KotlinParserDefinition; + +import javax.swing.*; + +public class KotlinStandaloneScriptRunConfigurationEditor extends SettingsEditor implements PanelWithAnchor { + private JPanel mainPanel; + private CommonJavaParametersPanel commonProgramParameters; + private AlternativeJREPanel alternativeJREPanel; + private TextFieldWithBrowseButton chooseScriptFileTextField; + private LabeledComponent chooseScriptFileComponent; + private JComponent anchor; + + public KotlinStandaloneScriptRunConfigurationEditor(Project project) { + initChooseFileField(project); + anchor = UIUtil.mergeComponentsWithAnchor(chooseScriptFileComponent, commonProgramParameters, alternativeJREPanel); + } + + void initChooseFileField(Project project) { + FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor() + .withFileFilter(new Condition() { + @Override + public boolean value(VirtualFile file) { + return file.isDirectory() || KotlinParserDefinition.STD_SCRIPT_SUFFIX.equals(file.getExtension()); + } + }) + .withTreeRootVisible(true); + + chooseScriptFileTextField.addBrowseFolderListener("Choose script file", null, project, descriptor, TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT); + } + + @Override + protected void resetEditorFrom(KotlinStandaloneScriptRunConfiguration configuration) { + commonProgramParameters.reset(configuration); + String path = configuration.filePath; + chooseScriptFileTextField.setText(path != null ? path : ""); + alternativeJREPanel.init(configuration.getAlternativeJrePath(), configuration.isAlternativeJrePathEnabled()); + } + + @Override + protected void applyEditorTo(KotlinStandaloneScriptRunConfiguration configuration) throws ConfigurationException { + commonProgramParameters.applyTo(configuration); + configuration.setAlternativeJrePath(alternativeJREPanel.getPath()); + configuration.setAlternativeJrePathEnabled(alternativeJREPanel.isPathEnabled()); + configuration.filePath = chooseScriptFileTextField.getText(); + } + + @NotNull + @Override + protected JComponent createEditor() { + return mainPanel; + } + + @Override + public JComponent getAnchor() { + return anchor; + } + + @Override + public void setAnchor(JComponent anchor) { + this.anchor = anchor; + commonProgramParameters.setAnchor(anchor); + alternativeJREPanel.setAnchor(anchor); + chooseScriptFileComponent.setAnchor(anchor); + } + + private void createUIComponents() { + chooseScriptFileComponent = new LabeledComponent(); + chooseScriptFileTextField = new TextFieldWithBrowseButton(); + chooseScriptFileComponent.setComponent(chooseScriptFileTextField); + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/run/script/standalone/KotlinStandaloneScriptRunConfigurationProducer.kt b/idea/src/org/jetbrains/kotlin/idea/run/script/standalone/KotlinStandaloneScriptRunConfigurationProducer.kt new file mode 100644 index 00000000000..f30c2a7b2d2 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/run/script/standalone/KotlinStandaloneScriptRunConfigurationProducer.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * 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. + */ + +package org.jetbrains.kotlin.idea.run.script.standalone + +import com.intellij.execution.actions.ConfigurationContext +import com.intellij.execution.actions.RunConfigurationProducer +import com.intellij.openapi.util.Ref +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.psiUtil.getParentOfType + +public class KotlinStandaloneScriptRunConfigurationProducer : + RunConfigurationProducer(KotlinStandaloneScriptRunConfigurationType.instance) { + override fun setupConfigurationFromContext( + configuration: KotlinStandaloneScriptRunConfiguration, + context: ConfigurationContext?, + sourceElement: Ref? + ): Boolean { + configuration.filePath = pathFromContext(context) ?: return false + configuration.setGeneratedName() + return true + } + + fun pathFromContext(context: ConfigurationContext?): String? { + val location = context?.location ?: return null + return pathFromPsiElement(location.psiElement) + } + + override fun isConfigurationFromContext(configuration: KotlinStandaloneScriptRunConfiguration?, context: ConfigurationContext?): Boolean { + val filePath = configuration?.filePath + return filePath != null && filePath == pathFromContext(context) + } + + companion object { + fun pathFromPsiElement(element: PsiElement): String? { + val file = element.getParentOfType(false) ?: return null + val script = file.script ?: return null + return script.getContainingKtFile().virtualFile.canonicalPath + } + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/run/script/standalone/KotlinStandaloneScriptRunConfigurationType.kt b/idea/src/org/jetbrains/kotlin/idea/run/script/standalone/KotlinStandaloneScriptRunConfigurationType.kt new file mode 100644 index 00000000000..194b9aff302 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/run/script/standalone/KotlinStandaloneScriptRunConfigurationType.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * 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. + */ + +package org.jetbrains.kotlin.idea.run.script.standalone + +import com.intellij.execution.configurations.* +import com.intellij.openapi.project.Project +import org.jetbrains.kotlin.idea.KotlinIcons + +class KotlinStandaloneScriptRunConfigurationType : ConfigurationTypeBase( + "KotlinStandaloneScriptRunConfigurationType", + "Kotlin script", + "Run Kotlin script", + KotlinIcons.LAUNCH +) { + init { + addFactory(Factory(this)) + } + + private class Factory(type: ConfigurationType) : ConfigurationFactory(type) { + override fun createTemplateConfiguration(project: Project): RunConfiguration { + return KotlinStandaloneScriptRunConfiguration(project, this, "") + } + } + + companion object { + val instance: KotlinStandaloneScriptRunConfigurationType + get() = ConfigurationTypeUtil.findConfigurationType(KotlinStandaloneScriptRunConfigurationType::class.java) + } +}