[PSI] Refactor import handling in code fragments

Now there is a single place that performs import modification, as well
as reports events related to that modification.
This commit is contained in:
Yan Zhulanow
2024-02-15 16:45:13 +09:00
committed by Space Team
parent fa511fdc52
commit 45c0fa8d23
@@ -28,7 +28,6 @@ import com.intellij.psi.tree.IElementType
import com.intellij.testFramework.LightVirtualFile
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
import java.util.*
abstract class KtCodeFragment(
private val myProject: Project,
@@ -44,7 +43,7 @@ abstract class KtCodeFragment(
}, false
), KtCodeFragmentBase {
private var viewProvider = super.getViewProvider() as SingleRootFileViewProvider
private var imports = LinkedHashSet<String>()
private val importDirectiveStrings = LinkedHashSet<String>()
private val fakeContextForJavaFile: PsiElement? by lazy {
this.getCopyableUserData(FAKE_CONTEXT_FOR_JAVA_FILE)?.invoke()
@@ -54,8 +53,9 @@ abstract class KtCodeFragment(
@Suppress("LeakingThis")
getViewProvider().forceCachedPsi(this)
init(TokenType.CODE_FRAGMENT, elementType)
if (context != null) {
initImports(imports)
if (imports != null) {
appendImports(imports)
}
}
@@ -100,7 +100,8 @@ abstract class KtCodeFragment(
return (cloneImpl(elementClone) as KtCodeFragment).apply {
isPhysical = false
myOriginalFile = this@KtCodeFragment
imports = this@KtCodeFragment.imports
importDirectiveStrings.clear()
importDirectiveStrings.addAll(this@KtCodeFragment.importDirectiveStrings)
viewProvider = SingleRootFileViewProvider(
PsiManager.getInstance(myProject),
LightVirtualFile(name, KotlinFileType.INSTANCE, text),
@@ -125,40 +126,64 @@ abstract class KtCodeFragment(
}
override fun importsToString(): String {
return imports.joinToString(IMPORT_SEPARATOR)
return importDirectiveStrings.joinToString(IMPORT_SEPARATOR)
}
override fun addImportsFromString(imports: String?) {
if (imports == null || imports.isEmpty()) return
if (imports != null && appendImports(imports)) {
// For K1: This forces the code fragment to be re-highlighted.
add(KtPsiFactory(project).createColon()).delete()
imports.split(IMPORT_SEPARATOR).forEach {
addImport(it)
// Increment the modification stamp
clearCaches()
}
// we need this code to force re-highlighting, otherwise it does not work by some reason
val tempElement = KtPsiFactory(project).createColon()
add(tempElement).delete()
}
@Deprecated("Use 'addImportsFromString()' instead", ReplaceWith("addImportsFromString(import)"), level = DeprecationLevel.WARNING)
fun addImport(import: String) {
val contextFile = getContextContainingFile()
if (contextFile != null) {
if (contextFile.importDirectives.find { it.text == import } == null) {
imports.add(import)
clearCaches() // Increment the modification stamp
addImportsFromString(import)
}
/**
* Parses raw [rawImports] and appends them to the list of code fragment imports.
*
* Import strings must be separated by the [IMPORT_SEPARATOR].
* Each import must be either a qualified name to import (e.g., 'foo.bar'), or a complete text representation of an import directive
* (e.g., 'import foo.bar as baz').
*
* Note that already present import directives will be ignored.
*
* @return `true` if new import directives were added.
*/
private fun appendImports(rawImports: String): Boolean {
if (rawImports.isEmpty()) {
return false
}
var hasNewImports = false
for (rawImport in rawImports.split(IMPORT_SEPARATOR)) {
val importDirectiveString = if (rawImport.startsWith("import ")) rawImport else "import $rawImport"
if (importDirectiveStrings.add(importDirectiveString) && !hasNewImports) {
hasNewImports = true
}
}
return hasNewImports
}
fun importsAsImportList(): KtImportList? {
if (imports.isNotEmpty() && context != null) {
return KtPsiFactory.contextual(context).createFile("imports_for_codeFragment.kt", imports.joinToString("\n")).importList
if (importDirectiveStrings.isNotEmpty() && context != null) {
val ktPsiFactory = KtPsiFactory.contextual(context)
val fileText = importDirectiveStrings.joinToString("\n")
return ktPsiFactory.createFile("imports_for_codeFragment.kt", fileText).importList
}
return null
}
override val importLists: List<KtImportList>
get() = importsAsImportList().let(::listOfNotNull)
get() = listOfNotNull(importsAsImportList())
override val importDirectives: List<KtImportDirective>
get() = importsAsImportList()?.imports ?: emptyList()
@@ -186,15 +211,6 @@ abstract class KtCodeFragment(
return contextElement
}
private fun initImports(imports: String?) {
if (imports != null && imports.isNotEmpty()) {
val importsWithPrefix = imports.split(IMPORT_SEPARATOR).map { it.takeIf { it.startsWith("import ") } ?: "import ${it.trim()}" }
importsWithPrefix.forEach {
addImport(it)
}
}
}
companion object {
const val IMPORT_SEPARATOR: String = ","