Build: fix API differences between 201 and 202 in NewKotlinFileAction

This commit is contained in:
Dmitriy Novozhilov
2020-11-17 16:13:33 +03:00
parent e4e28a5495
commit d50d56f68c
12 changed files with 283 additions and 20 deletions
@@ -25,6 +25,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.util.Collection;
@WithMutedInDatabaseRunTest
@@ -46,7 +47,7 @@ public abstract class KotlinLightCodeInsightFixtureTestCaseBase extends LightCod
return super.getFile();
}
protected final Collection<File> myFilesToDelete = new THashSet<>();
protected final Collection<Path> myFilesToDelete = new THashSet<>();
private final TempFiles myTempFiles = new TempFiles(myFilesToDelete);
@Override
@@ -65,7 +66,7 @@ public abstract class KotlinLightCodeInsightFixtureTestCaseBase extends LightCod
File temp = FileUtil.createTempFile("copy", "." + ext);
setContentOnDisk(temp, bom, content, charset);
myFilesToDelete.add(temp);
myFilesToDelete.add(temp.toPath());
final VirtualFile file = getVirtualFile(temp);
assert file != null : temp;
return file;
@@ -25,7 +25,6 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.util.Collection;
@WithMutedInDatabaseRunTest
@@ -47,7 +46,7 @@ public abstract class KotlinLightCodeInsightFixtureTestCaseBase extends LightCod
return super.getFile();
}
protected final Collection<Path> myFilesToDelete = new THashSet<>();
protected final Collection<File> myFilesToDelete = new THashSet<>();
private final TempFiles myTempFiles = new TempFiles(myFilesToDelete);
@Override
@@ -66,7 +65,7 @@ public abstract class KotlinLightCodeInsightFixtureTestCaseBase extends LightCod
File temp = FileUtil.createTempFile("copy", "." + ext);
setContentOnDisk(temp, bom, content, charset);
myFilesToDelete.add(temp.toPath());
myFilesToDelete.add(temp);
final VirtualFile file = getVirtualFile(temp);
assert file != null : temp;
return file;
@@ -47,10 +47,10 @@ class NewKotlinFileAction : CreateFileFromTemplateAction(
KotlinBundle.message("action.new.file.description"),
KotlinFileType.INSTANCE.icon
), DumbAware {
override fun postProcess(createdElement: PsiFile?, templateName: String?, customProperties: Map<String, String>?) {
override fun postProcess(createdElement: PsiFile, templateName: String?, customProperties: Map<String, String>?) {
super.postProcess(createdElement, templateName, customProperties)
val module = ModuleUtilCore.findModuleForPsiElement(createdElement!!)
val module = ModuleUtilCore.findModuleForPsiElement(createdElement)
if (createdElement is KtFile) {
if (module != null) {
@@ -0,0 +1,263 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.actions
import com.intellij.ide.actions.CreateFileFromTemplateAction
import com.intellij.ide.actions.CreateFileFromTemplateDialog
import com.intellij.ide.actions.CreateFromTemplateAction
import com.intellij.ide.fileTemplates.FileTemplate
import com.intellij.ide.fileTemplates.FileTemplateManager
import com.intellij.ide.fileTemplates.actions.AttributesDefaults
import com.intellij.ide.fileTemplates.ui.CreateFromTemplateDialog
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.actionSystem.LangDataKeys
import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.editor.LogicalPosition
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ProjectRootManager
import com.intellij.openapi.ui.InputValidatorEx
import com.intellij.psi.PsiDirectory
import com.intellij.psi.PsiFile
import com.intellij.util.IncorrectOperationException
import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.idea.KotlinBundle
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.KotlinIcons
import org.jetbrains.kotlin.idea.statistics.FUSEventGroups
import org.jetbrains.kotlin.idea.statistics.KotlinFUSLogger
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.parsing.KotlinParserDefinition.Companion.STD_SCRIPT_SUFFIX
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import java.util.*
class NewKotlinFileAction : CreateFileFromTemplateAction(
KotlinBundle.message("action.new.file.text"),
KotlinBundle.message("action.new.file.description"),
KotlinFileType.INSTANCE.icon
), DumbAware {
override fun postProcess(createdElement: PsiFile?, templateName: String?, customProperties: Map<String, String>?) {
super.postProcess(createdElement, templateName, customProperties)
val module = ModuleUtilCore.findModuleForPsiElement(createdElement!!)
if (createdElement is KtFile) {
if (module != null) {
for (hook in NewKotlinFileHook.EP_NAME.extensions) {
hook.postProcess(createdElement, module)
}
}
val ktClass = createdElement.declarations.singleOrNull() as? KtNamedDeclaration
if (ktClass != null) {
CreateFromTemplateAction.moveCaretAfterNameIdentifier(ktClass)
} else {
val editor = FileEditorManager.getInstance(createdElement.project).selectedTextEditor ?: return
if (editor.document == createdElement.viewProvider.document) {
val lineCount = editor.document.lineCount
if (lineCount > 0) {
editor.caretModel.moveToLogicalPosition(LogicalPosition(lineCount - 1, 0))
}
}
}
}
}
override fun buildDialog(project: Project, directory: PsiDirectory, builder: CreateFileFromTemplateDialog.Builder) {
builder.setTitle(KotlinBundle.message("action.new.file.dialog.title"))
.addKind(
KotlinBundle.message("action.new.file.dialog.class.title"),
KotlinIcons.CLASS,
"Kotlin Class"
)
.addKind(
KotlinBundle.message("action.new.file.dialog.file.title"),
KotlinFileType.INSTANCE.icon,
"Kotlin File"
)
.addKind(
KotlinBundle.message("action.new.file.dialog.interface.title"),
KotlinIcons.INTERFACE,
"Kotlin Interface"
)
.addKind(
KotlinBundle.message("action.new.file.dialog.data.class.title"),
KotlinIcons.CLASS,
"Kotlin Data Class"
)
.addKind(
KotlinBundle.message("action.new.file.dialog.enum.title"),
KotlinIcons.ENUM,
"Kotlin Enum"
)
.addKind(
KotlinBundle.message("action.new.file.dialog.sealed.class.title"),
KotlinIcons.CLASS,
"Kotlin Sealed Class"
)
.addKind(
KotlinBundle.message("action.new.file.dialog.annotation.title"),
KotlinIcons.ANNOTATION,
"Kotlin Annotation"
)
.addKind(
KotlinBundle.message("action.new.file.dialog.object.title"),
KotlinIcons.OBJECT,
"Kotlin Object"
)
builder.setValidator(NameValidator)
}
override fun getActionName(directory: PsiDirectory, newName: String, templateName: String): String =
KotlinBundle.message("action.new.file.text")
override fun isAvailable(dataContext: DataContext): Boolean {
if (super.isAvailable(dataContext)) {
val ideView = LangDataKeys.IDE_VIEW.getData(dataContext)!!
val project = PlatformDataKeys.PROJECT.getData(dataContext)!!
val projectFileIndex = ProjectRootManager.getInstance(project).fileIndex
return ideView.directories.any { projectFileIndex.isInSourceContent(it.virtualFile) }
}
return false
}
override fun hashCode(): Int = 0
override fun equals(other: Any?): Boolean = other is NewKotlinFileAction
override fun startInWriteAction() = false
override fun createFileFromTemplate(name: String, template: FileTemplate, dir: PsiDirectory) =
createFileFromTemplateWithStat(name, template, dir)
companion object {
private object NameValidator : InputValidatorEx {
override fun getErrorText(inputString: String): String? {
if (inputString.trim().isEmpty()) {
return KotlinBundle.message("action.new.file.error.empty.name")
}
val parts: List<String> = inputString.split(*FQNAME_SEPARATORS)
if (parts.any { it.trim().isEmpty() }) {
return KotlinBundle.message("action.new.file.error.empty.name.part")
}
return null
}
override fun checkInput(inputString: String): Boolean = true
override fun canClose(inputString: String): Boolean = getErrorText(inputString) == null
}
@get:TestOnly
val nameValidator: InputValidatorEx
get() = NameValidator
private fun findOrCreateTarget(dir: PsiDirectory, name: String, directorySeparators: CharArray): Pair<String, PsiDirectory> {
var className = removeKotlinExtensionIfPresent(name)
var targetDir = dir
for (splitChar in directorySeparators) {
if (splitChar in className) {
val names = className.trim().split(splitChar)
for (dirName in names.dropLast(1)) {
targetDir = targetDir.findSubdirectory(dirName) ?: runWriteAction {
targetDir.createSubdirectory(dirName)
}
}
className = names.last()
break
}
}
return Pair(className, targetDir)
}
private fun removeKotlinExtensionIfPresent(name: String): String = when {
name.endsWith(".$KOTLIN_WORKSHEET_EXTENSION") -> name.removeSuffix(".$KOTLIN_WORKSHEET_EXTENSION")
name.endsWith(".$STD_SCRIPT_SUFFIX") -> name.removeSuffix(".$STD_SCRIPT_SUFFIX")
name.endsWith(".${KotlinFileType.EXTENSION}") -> name.removeSuffix(".${KotlinFileType.EXTENSION}")
else -> name
}
private fun createFromTemplate(dir: PsiDirectory, className: String, template: FileTemplate): PsiFile? {
val project = dir.project
val defaultProperties = FileTemplateManager.getInstance(project).defaultProperties
val properties = Properties(defaultProperties)
val element = try {
CreateFromTemplateDialog(
project, dir, template,
AttributesDefaults(className).withFixedName(true),
properties
).create()
} catch (e: IncorrectOperationException) {
throw e
} catch (e: Exception) {
LOG.error(e)
return null
}
return element?.containingFile
}
private val FILE_SEPARATORS = charArrayOf('/', '\\')
private val FQNAME_SEPARATORS = charArrayOf('/', '\\', '.')
fun createFileFromTemplateWithStat(name: String, template: FileTemplate, dir: PsiDirectory): PsiFile? {
KotlinFUSLogger.log(FUSEventGroups.NewFileTemplate, template.name)
return createFileFromTemplate(name, template, dir)
}
fun createFileFromTemplate(name: String, template: FileTemplate, dir: PsiDirectory): PsiFile? {
val directorySeparators = when (template.name) {
"Kotlin File" -> FILE_SEPARATORS
else -> FQNAME_SEPARATORS
}
val (className, targetDir) = findOrCreateTarget(dir, name, directorySeparators)
val service = DumbService.getInstance(dir.project)
service.isAlternativeResolveEnabled = true
try {
val psiFile = createFromTemplate(targetDir, className, template)
if (psiFile is KtFile) {
val singleClass = psiFile.declarations.singleOrNull() as? KtClass
if (singleClass != null && !singleClass.isEnum() && !singleClass.isInterface() && name.contains("Abstract")) {
runWriteAction {
singleClass.addModifier(KtTokens.ABSTRACT_KEYWORD)
}
}
}
return psiFile
} finally {
service.isAlternativeResolveEnabled = false
}
}
}
}
abstract class NewKotlinFileHook {
companion object {
val EP_NAME: ExtensionPointName<NewKotlinFileHook> =
ExtensionPointName.create<NewKotlinFileHook>("org.jetbrains.kotlin.newFileHook")
}
abstract fun postProcess(createdElement: KtFile, module: Module)
}
@@ -15,7 +15,7 @@ abstract class AbstractConfigureKotlinInTempDirTest : AbstractConfigureKotlinTes
override fun getProjectDirOrFile(): Path {
val tempDir = FileUtil.generateRandomTemporaryPath()
FileUtil.createTempDirectory("temp", null)
myFilesToDelete.add(tempDir)
myFilesToDelete.add(tempDir.toPath())
FileUtil.copyDir(File(projectRoot), tempDir)
@@ -35,4 +35,4 @@ abstract class AbstractConfigureKotlinInTempDirTest : AbstractConfigureKotlinTes
return File(projectFilePath).toPath()
}
}
}
@@ -15,7 +15,7 @@ abstract class AbstractConfigureKotlinInTempDirTest : AbstractConfigureKotlinTes
override fun getProjectDirOrFile(): Path {
val tempDir = FileUtil.generateRandomTemporaryPath()
FileUtil.createTempDirectory("temp", null)
myFilesToDelete.add(tempDir.toPath())
myFilesToDelete.add(tempDir)
FileUtil.copyDir(File(projectRoot), tempDir)
@@ -237,14 +237,14 @@ abstract class AbstractConfigureKotlinTest : PlatformTestCase() {
private val pathToNonexistentRuntimeJar: String
get() {
val pathToTempKotlinRuntimeJar = FileUtil.getTempDirectory() + "/" + PathUtil.KOTLIN_JAVA_STDLIB_JAR
myFilesToDelete.add(File(pathToTempKotlinRuntimeJar))
myFilesToDelete.add(File(pathToTempKotlinRuntimeJar).toPath())
return pathToTempKotlinRuntimeJar
}
private val pathToNonexistentJsJar: String
get() {
val pathToTempKotlinRuntimeJar = FileUtil.getTempDirectory() + "/" + PathUtil.JS_LIB_JAR_NAME
myFilesToDelete.add(File(pathToTempKotlinRuntimeJar))
myFilesToDelete.add(File(pathToTempKotlinRuntimeJar).toPath())
return pathToTempKotlinRuntimeJar
}
@@ -237,14 +237,14 @@ abstract class AbstractConfigureKotlinTest : PlatformTestCase() {
private val pathToNonexistentRuntimeJar: String
get() {
val pathToTempKotlinRuntimeJar = FileUtil.getTempDirectory() + "/" + PathUtil.KOTLIN_JAVA_STDLIB_JAR
myFilesToDelete.add(File(pathToTempKotlinRuntimeJar).toPath())
myFilesToDelete.add(File(pathToTempKotlinRuntimeJar))
return pathToTempKotlinRuntimeJar
}
private val pathToNonexistentJsJar: String
get() {
val pathToTempKotlinRuntimeJar = FileUtil.getTempDirectory() + "/" + PathUtil.JS_LIB_JAR_NAME
myFilesToDelete.add(File(pathToTempKotlinRuntimeJar).toPath())
myFilesToDelete.add(File(pathToTempKotlinRuntimeJar))
return pathToTempKotlinRuntimeJar
}
@@ -39,7 +39,7 @@ class QuickDocInHierarchyTest() : CodeInsightTestCase() {
val provider = BrowseHierarchyActionBase.findProvider(LanguageTypeHierarchy.INSTANCE, file, file, context)!!
val hierarchyTreeStructure = TypeHierarchyTreeStructure(
project,
provider.getTarget(context) as PsiClass?,
provider.getTarget(context) as PsiClass,
HierarchyBrowserBaseEx.SCOPE_PROJECT
)
val hierarchyNodeDescriptor = hierarchyTreeStructure.baseDescriptor as TypeHierarchyNodeDescriptor
@@ -47,4 +47,4 @@ class QuickDocInHierarchyTest() : CodeInsightTestCase() {
TestCase.assertTrue("Invalid doc\n: $doc", doc.contains("Very special class"))
}
}
}
@@ -39,7 +39,7 @@ class QuickDocInHierarchyTest() : CodeInsightTestCase() {
val provider = BrowseHierarchyActionBase.findProvider(LanguageTypeHierarchy.INSTANCE, file, file, context)!!
val hierarchyTreeStructure = TypeHierarchyTreeStructure(
project,
provider.getTarget(context) as PsiClass,
provider.getTarget(context) as PsiClass?,
HierarchyBrowserBaseEx.SCOPE_PROJECT
)
val hierarchyNodeDescriptor = hierarchyTreeStructure.baseDescriptor as TypeHierarchyNodeDescriptor
@@ -5,7 +5,7 @@ import org.jetbrains.uast.*
import org.jetbrains.uast.test.common.UElementToParentMap
import org.jetbrains.uast.test.common.kotlin.IdentifiersTestBase
import org.jetbrains.uast.test.common.visitUFileAndGetResult
import org.jetbrains.uast.test.env.assertEqualsToFile
import org.jetbrains.uast.test.env.kotlin.assertEqualsToFile
import java.io.File
import kotlin.test.assertNotNull
@@ -34,4 +34,4 @@ private fun refNameRetriever(psiElement: PsiElement): UElement? =
fun UFile.asRefNames() = object : UElementToParentMap(::refNameRetriever) {
override fun renderSource(element: PsiElement): String = element.javaClass.simpleName
}.visitUFileAndGetResult(this)
}.visitUFileAndGetResult(this)
@@ -1,11 +1,11 @@
package org.jetbrains.uast.test.kotlin
import com.intellij.psi.PsiElement
import com.intellij.testFramework.assertEqualsToFile
import org.jetbrains.uast.*
import org.jetbrains.uast.test.common.UElementToParentMap
import org.jetbrains.uast.test.common.kotlin.IdentifiersTestBase
import org.jetbrains.uast.test.common.visitUFileAndGetResult
import org.jetbrains.uast.test.env.assertEqualsToFile
import java.io.File
import kotlin.test.assertNotNull