Register custom file extension for scripts loaded from script template

This commit is contained in:
Natalia Selezneva
2018-07-05 12:18:58 +03:00
parent be2d4964fd
commit 41dc059da9
7 changed files with 80 additions and 15 deletions
@@ -18,10 +18,13 @@ package org.jetbrains.kotlin.idea.core.script
import com.intellij.ide.projectView.impl.ProjectRootsUtil.isInTestSource import com.intellij.ide.projectView.impl.ProjectRootsUtil.isInTestSource
import com.intellij.ide.scratch.ScratchFileService import com.intellij.ide.scratch.ScratchFileService
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.components.ServiceManager import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.extensions.Extensions import com.intellij.openapi.extensions.Extensions
import com.intellij.openapi.fileTypes.FileTypeManager
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.JavaSdk import com.intellij.openapi.projectRoots.JavaSdk
import com.intellij.openapi.projectRoots.ProjectJdkTable import com.intellij.openapi.projectRoots.ProjectJdkTable
@@ -29,21 +32,18 @@ import com.intellij.openapi.projectRoots.ex.PathUtilEx
import com.intellij.openapi.roots.ProjectRootManager import com.intellij.openapi.roots.ProjectRootManager
import com.intellij.openapi.vfs.VfsUtil import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.caches.project.SdkInfo import org.jetbrains.kotlin.idea.caches.project.SdkInfo
import org.jetbrains.kotlin.idea.caches.project.getScriptRelatedModuleInfo import org.jetbrains.kotlin.idea.caches.project.getScriptRelatedModuleInfo
import org.jetbrains.kotlin.script.*
import org.jetbrains.kotlin.scripting.compiler.plugin.KotlinScriptDefinitionAdapterFromNewAPI
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil.isInContent import org.jetbrains.kotlin.idea.util.ProjectRootsUtil.isInContent
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.script.KotlinScriptDefinition import org.jetbrains.kotlin.script.*
import org.jetbrains.kotlin.script.KotlinScriptDefinitionFromAnnotatedTemplate import org.jetbrains.kotlin.scripting.compiler.plugin.KotlinScriptDefinitionAdapterFromNewAPI
import org.jetbrains.kotlin.script.ScriptDefinitionProvider import org.jetbrains.kotlin.scripting.compiler.plugin.KotlinScriptDefinitionAdapterFromNewAPIBase
import org.jetbrains.kotlin.script.ScriptTemplatesProvider
import org.jetbrains.kotlin.utils.PathUtil import org.jetbrains.kotlin.utils.PathUtil
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import org.jetbrains.kotlin.utils.addToStdlib.flattenTo import org.jetbrains.kotlin.utils.addToStdlib.flattenTo
import java.io.File import java.io.File
import java.lang.reflect.InvocationTargetException
import java.net.URLClassLoader import java.net.URLClassLoader
import kotlin.concurrent.write import kotlin.concurrent.write
import kotlin.script.dependencies.Environment import kotlin.script.dependencies.Environment
@@ -133,6 +133,25 @@ class ScriptDefinitionsManager(private val project: Project) : LazyScriptDefinit
private fun updateDefinitions() { private fun updateDefinitions() {
assert(lock.isWriteLocked) { "updateDefinitions should only be called under the write lock" } assert(lock.isWriteLocked) { "updateDefinitions should only be called under the write lock" }
definitions = definitionsByContributor.values.flattenTo(mutableListOf()).asSequence() definitions = definitionsByContributor.values.flattenTo(mutableListOf()).asSequence()
// Register new file extensions
val fileTypeManager = FileTypeManager.getInstance()
val extensions = definitions?.mapNotNull { definition ->
(definition as? KotlinScriptDefinitionAdapterFromNewAPIBase)
?.scriptFileExtensionWithDot?.removePrefix(".")
?.takeIf { fileTypeManager.getFileTypeByExtension(it) != KotlinFileType.INSTANCE }
}?.toList()
if (extensions?.isNotEmpty() == true) {
ApplicationManager.getApplication().invokeLater {
runWriteAction {
extensions.forEach {
fileTypeManager.associateExtension(KotlinFileType.INSTANCE, it)
}
}
}
}
clearCache() clearCache()
// TODO: clear by script type/definition // TODO: clear by script type/definition
ServiceManager.getService(project, ScriptDependenciesCache::class.java).clear() ServiceManager.getService(project, ScriptDependenciesCache::class.java).clear()
@@ -0,0 +1 @@
1 <error>+</error> "a"
@@ -0,0 +1,14 @@
package custom.scriptDefinition
import java.io.File
import kotlin.script.dependencies.*
import kotlin.script.experimental.dependencies.*
import kotlin.script.experimental.location.*
import kotlin.script.templates.ScriptTemplateDefinition
import kotlin.script.experimental.annotations.KotlinScript
import kotlin.script.experimental.annotations.KotlinScriptEvaluator
import kotlin.script.experimental.annotations.KotlinScriptFileExtension
@KotlinScript("Kotlin Script with custom extension")
@KotlinScriptFileExtension("mykts")
open class Template
@@ -27,6 +27,7 @@ import com.intellij.openapi.roots.libraries.Library
import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.newvfs.impl.VfsRootAccess import com.intellij.openapi.vfs.newvfs.impl.VfsRootAccess
import com.intellij.psi.PsiManager
import com.intellij.testFramework.ModuleTestCase import com.intellij.testFramework.ModuleTestCase
import com.intellij.testFramework.PlatformTestCase import com.intellij.testFramework.PlatformTestCase
import com.intellij.testFramework.PsiTestUtil import com.intellij.testFramework.PsiTestUtil
@@ -429,7 +430,9 @@ class IdeaModuleInfoTest : ModuleTestCase() {
) = ModuleRootModificationUtil.addDependency(this, other, dependencyScope, exported) ) = ModuleRootModificationUtil.addDependency(this, other, dependencyScope, exported)
private val VirtualFile.moduleInfo: IdeaModuleInfo private val VirtualFile.moduleInfo: IdeaModuleInfo
get() = getModuleInfoByVirtualFile(project, this)!! get() {
return PsiManager.getInstance(project).findFile(this)!!.getModuleInfo()
}
private val Module.production: ModuleProductionSourceInfo private val Module.production: ModuleProductionSourceInfo
get() = productionSourceInfo()!! get() = productionSourceInfo()!!
@@ -31,6 +31,7 @@ import com.intellij.testFramework.PlatformTestCase
import com.intellij.testFramework.PlatformTestUtil import com.intellij.testFramework.PlatformTestUtil
import com.intellij.testFramework.PsiTestUtil import com.intellij.testFramework.PsiTestUtil
import com.intellij.testFramework.exceptionCases.AbstractExceptionCase import com.intellij.testFramework.exceptionCases.AbstractExceptionCase
import com.intellij.util.ui.UIUtil
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime
import org.jetbrains.kotlin.idea.completion.test.KotlinCompletionTestCase import org.jetbrains.kotlin.idea.completion.test.KotlinCompletionTestCase
import org.jetbrains.kotlin.idea.core.script.ScriptDefinitionContributor import org.jetbrains.kotlin.idea.core.script.ScriptDefinitionContributor
@@ -46,6 +47,9 @@ import org.jetbrains.kotlin.test.util.addDependency
import org.jetbrains.kotlin.test.util.projectLibrary import org.jetbrains.kotlin.test.util.projectLibrary
import org.jetbrains.kotlin.test.util.renderAsGotoImplementation import org.jetbrains.kotlin.test.util.renderAsGotoImplementation
import org.jetbrains.kotlin.utils.PathUtil import org.jetbrains.kotlin.utils.PathUtil
import org.jetbrains.kotlin.utils.PathUtil.KOTLIN_JAVA_SCRIPT_RUNTIME_JAR
import org.jetbrains.kotlin.utils.PathUtil.KOTLIN_SCRIPTING_COMMON_JAR
import org.jetbrains.kotlin.utils.PathUtil.KOTLIN_SCRIPTING_JVM_JAR
import org.junit.Assert import org.junit.Assert
import org.junit.ComparisonFailure import org.junit.ComparisonFailure
import java.io.File import java.io.File
@@ -63,7 +67,8 @@ abstract class AbstractScriptConfigurationHighlightingTest : AbstractScriptConfi
checkHighlighting( checkHighlighting(
editor, editor,
InTextDirectivesUtils.isDirectiveDefined(file.text, "// CHECK_WARNINGS"), InTextDirectivesUtils.isDirectiveDefined(file.text, "// CHECK_WARNINGS"),
InTextDirectivesUtils.isDirectiveDefined(file.text, "// CHECK_INFOS")) InTextDirectivesUtils.isDirectiveDefined(file.text, "// CHECK_INFOS")
)
} }
fun doComplexTest(path: String) { fun doComplexTest(path: String) {
@@ -131,8 +136,13 @@ abstract class AbstractScriptConfigurationTest : KotlinCompletionTestCase() {
// do not create default module // do not create default module
} }
private fun findMainScript(testDir: String) = File(testDir).walkTopDown().find { it.name == SCRIPT_NAME } private fun findMainScript(testDir: String): File {
val scriptFile = File(testDir).walkTopDown().find { it.name == SCRIPT_NAME }
if (scriptFile != null) return scriptFile
return File(testDir).walkTopDown().singleOrNull { it.name.contains("script") }
?: error("Couldn't find $SCRIPT_NAME file in $testDir") ?: error("Couldn't find $SCRIPT_NAME file in $testDir")
}
protected fun configureScriptFile(path: String) { protected fun configureScriptFile(path: String) {
val mainScriptFile = findMainScript(path) val mainScriptFile = findMainScript(path)
@@ -217,9 +227,9 @@ abstract class AbstractScriptConfigurationTest : KotlinCompletionTestCase() {
private fun defaultEnvironment(path: String): Map<String, File?> { private fun defaultEnvironment(path: String): Map<String, File?> {
val templateOutDir = File("${path}template").takeIf { it.isDirectory }?.let { val templateOutDir = File("${path}template").takeIf { it.isDirectory }?.let {
compileLibToDir(it, PathUtil.kotlinPathsForDistDirectory.scriptRuntimePath.path) compileLibToDir(it, *scriptClasspath())
} ?: File("idea/testData/script/definition/defaultTemplate").takeIf { it.isDirectory }?.let { } ?: File("idea/testData/script/definition/defaultTemplate").takeIf { it.isDirectory }?.let {
compileLibToDir(it, PathUtil.kotlinPathsForDistDirectory.scriptRuntimePath.path) compileLibToDir(it, *scriptClasspath())
} }
val libSrcDir = File("${path}lib").takeIf { it.isDirectory } val libSrcDir = File("${path}lib").takeIf { it.isDirectory }
@@ -235,14 +245,24 @@ abstract class AbstractScriptConfigurationTest : KotlinCompletionTestCase() {
) )
} }
private fun scriptClasspath(): Array<String> {
return with(PathUtil.kotlinPathsForDistDirectory) {
arrayOf(
File(libPath, KOTLIN_JAVA_SCRIPT_RUNTIME_JAR).path,
File(libPath, KOTLIN_SCRIPTING_COMMON_JAR).path,
File(libPath, KOTLIN_SCRIPTING_JVM_JAR).path
)
}
}
private fun createFileAndSyncDependencies(scriptFile: File) { private fun createFileAndSyncDependencies(scriptFile: File) {
var script: VirtualFile? = null var script: VirtualFile? = null
if (module != null) { if (module != null) {
script = module.moduleFile?.parent?.findChild(SCRIPT_NAME) script = module.moduleFile?.parent?.findChild(scriptFile.name)
} }
if (script == null) { if (script == null) {
val target = File(project.basePath, SCRIPT_NAME) val target = File(project.basePath, scriptFile.name)
scriptFile.copyTo(target) scriptFile.copyTo(target)
script = LocalFileSystem.getInstance().findFileByPath(target.path) script = LocalFileSystem.getInstance().findFileByPath(target.path)
} }
@@ -287,6 +307,9 @@ abstract class AbstractScriptConfigurationTest : KotlinCompletionTestCase() {
provider, provider,
testRootDisposable testRootDisposable
) )
ScriptDefinitionsManager.getInstance(project).reloadScriptDefinitions() ScriptDefinitionsManager.getInstance(project).reloadScriptDefinitions()
UIUtil.dispatchAllInvocationEvents()
} }
} }
@@ -56,6 +56,11 @@ public class ScriptConfigurationHighlightingTestGenerated extends AbstractScript
runTest("idea/testData/script/definition/highlighting/customBaseClass/"); runTest("idea/testData/script/definition/highlighting/customBaseClass/");
} }
@TestMetadata("customExtension")
public void testCustomExtension() throws Exception {
runTest("idea/testData/script/definition/highlighting/customExtension/");
}
@TestMetadata("customLibrary") @TestMetadata("customLibrary")
public void testCustomLibrary() throws Exception { public void testCustomLibrary() throws Exception {
runTest("idea/testData/script/definition/highlighting/customLibrary/"); runTest("idea/testData/script/definition/highlighting/customLibrary/");
@@ -24,7 +24,7 @@ abstract class KotlinScriptDefinitionAdapterFromNewAPIBase : KotlinScriptDefinit
protected abstract val scriptDefinition: ScriptDefinition protected abstract val scriptDefinition: ScriptDefinition
protected abstract val scriptFileExtensionWithDot: String abstract val scriptFileExtensionWithDot: String
open val baseClass: KClass<*> by lazy(LazyThreadSafetyMode.PUBLICATION) { open val baseClass: KClass<*> by lazy(LazyThreadSafetyMode.PUBLICATION) {
getScriptingClass(scriptDefinition.compilationConfigurator.defaultConfiguration[ScriptingEnvironmentProperties.baseClass]) getScriptingClass(scriptDefinition.compilationConfigurator.defaultConfiguration[ScriptingEnvironmentProperties.baseClass])