Allow to run kts files as scripts from ide

Introduce KotlinStandaloneScriptRunConfiguration: configuration, type, producer, settings editor
This commit is contained in:
Pavel V. Talanov
2015-11-19 19:38:18 +03:00
parent 337701670c
commit 34793e63c7
6 changed files with 439 additions and 0 deletions
+2
View File
@@ -456,9 +456,11 @@
<lang.documentationProvider language="JAVA" implementationClass="org.jetbrains.kotlin.idea.KotlinQuickDocumentationProvider" order="first"/>
<documentationProvider implementation="org.jetbrains.kotlin.idea.KotlinQuickDocumentationProvider"/>
<configurationType implementation="org.jetbrains.kotlin.idea.run.JetRunConfigurationType"/>
<configurationType implementation="org.jetbrains.kotlin.idea.run.script.standalone.KotlinStandaloneScriptRunConfigurationType"/>
<configurationType implementation="org.jetbrains.kotlin.idea.k2jsrun.K2JSRunConfigurationType"/>
<programRunner implementation="org.jetbrains.kotlin.idea.k2jsrun.K2JSBrowserProgramRunner"/>
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.run.KotlinRunConfigurationProducer"/>
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.run.script.standalone.KotlinStandaloneScriptRunConfigurationProducer"/>
<codeInsight.lineMarkerProvider language="kotlin" implementationClass="org.jetbrains.kotlin.idea.highlighter.markers.KotlinLineMarkerProvider"/>
<codeInsight.lineMarkerProvider language="kotlin" implementationClass="org.jetbrains.kotlin.idea.highlighter.KotlinRecursiveCallLineMarkerProvider"/>
@@ -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<String, String> = 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<String, String>) {
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<out RunConfiguration> {
val group = SettingsEditorGroup<KotlinStandaloneScriptRunConfiguration>()
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<KotlinStandaloneScriptRunConfiguration>(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
}
}
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.kotlin.idea.run.script.standalone.KotlinStandaloneScriptRunConfigurationEditor">
<grid id="27dc6" binding="mainPanel" layout-manager="GridLayoutManager" row-count="4" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<xy x="20" y="20" width="811" height="400"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<vspacer id="f22cb">
<constraints>
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
</constraints>
</vspacer>
<component id="d9fac" class="com.intellij.execution.ui.CommonJavaParametersPanel" binding="commonProgramParameters">
<constraints>
<grid row="1" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
</component>
<component id="b06d8" class="com.intellij.execution.ui.AlternativeJREPanel" binding="alternativeJREPanel">
<constraints>
<grid row="2" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
</component>
<component id="872aa" class="com.intellij.openapi.ui.LabeledComponent" binding="chooseScriptFileComponent" custom-create="true">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<labelLocation value="West"/>
<text value="Script file"/>
</properties>
</component>
</children>
</grid>
</form>
@@ -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<KotlinStandaloneScriptRunConfiguration> implements PanelWithAnchor {
private JPanel mainPanel;
private CommonJavaParametersPanel commonProgramParameters;
private AlternativeJREPanel alternativeJREPanel;
private TextFieldWithBrowseButton chooseScriptFileTextField;
private LabeledComponent<TextFieldWithBrowseButton> 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<VirtualFile>() {
@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<TextFieldWithBrowseButton>();
chooseScriptFileTextField = new TextFieldWithBrowseButton();
chooseScriptFileComponent.setComponent(chooseScriptFileTextField);
}
}
@@ -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<KotlinStandaloneScriptRunConfiguration>(KotlinStandaloneScriptRunConfigurationType.instance) {
override fun setupConfigurationFromContext(
configuration: KotlinStandaloneScriptRunConfiguration,
context: ConfigurationContext?,
sourceElement: Ref<PsiElement>?
): 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<KtFile>(false) ?: return null
val script = file.script ?: return null
return script.getContainingKtFile().virtualFile.canonicalPath
}
}
}
@@ -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)
}
}