Fix ExtractDeclarationFromCurrentFileIntention assertion

Refactorings should be invoked inside transaction 'exception occurs when extracting sealed class from file with the same name assertion failed when refactoring tries to use MoveKotlinTopLevelDeclarationsDialog
+ move refactorings
+ small improvement of applicability range calculation

Fixed #KT-33357
This commit is contained in:
Igor Yakovlev
2019-08-30 14:31:17 +03:00
parent 9594e9b3b1
commit 02a6a1ea49
@@ -11,7 +11,10 @@ import com.intellij.codeInsight.navigation.NavigationUtil
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiDirectory
import com.intellij.psi.PsiFile
import com.intellij.refactoring.move.MoveCallback
import com.intellij.refactoring.util.CommonRefactoringUtil
import kotlinx.coroutines.runBlocking
@@ -22,7 +25,9 @@ import org.jetbrains.kotlin.idea.core.moveCaret
import org.jetbrains.kotlin.idea.intentions.SelfTargetingRangeIntention
import org.jetbrains.kotlin.idea.refactoring.createKotlinFile
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui.MoveKotlinTopLevelDeclarationsDialog
import org.jetbrains.kotlin.idea.refactoring.showWithTransaction
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.endOffset
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
@@ -47,11 +52,10 @@ class ExtractDeclarationFromCurrentFileIntention :
}
override fun applicabilityRange(element: KtClassOrObject): TextRange? {
if (element.name == null) return null
element.name ?: return null
if (element.parent !is KtFile) return null
if (element.hasModifier(KtTokens.PRIVATE_KEYWORD)) return null
if (element.containingKtFile.declarations.size == 1) return null
if (element.containingKtFile.run { declarations.size == 1 || containingDirectory === null }) return null
val extraClassesToMove = element.tryGetExtraClassesToMove() ?: return null
val startOffset = when (element) {
@@ -71,7 +75,7 @@ class ExtractDeclarationFromCurrentFileIntention :
override fun startInWriteAction() = false
override fun applyTo(element: KtClassOrObject, editor: Editor?) {
if (editor == null) throw IllegalArgumentException("This intention requires an editor")
requireNotNull(editor) { "This intention requires an editor" }
val file = element.containingKtFile
val project = file.project
val originalOffset = editor.caretModel.offset - element.startOffset
@@ -79,38 +83,17 @@ class ExtractDeclarationFromCurrentFileIntention :
val packageName = file.packageFqName
val targetFileName = "${element.name}.kt"
val targetFile = directory.findFile(targetFileName)
if (targetFile != null) {
if (targetFile !== null) {
if (ApplicationManager.getApplication().isUnitTestMode) {
throw CommonRefactoringUtil.RefactoringErrorHintException("File $targetFileName already exists")
}
// If automatic move is not possible, fall back to full-fledged Move Declarations refactoring
ApplicationManager.getApplication().invokeLater {
MoveKotlinTopLevelDeclarationsDialog(
project,
setOf(element),
packageName.asString(),
directory,
targetFile as? KtFile,
true,
true,
true,
true,
MoveCallback {
runBlocking {
withTimeoutOrNull(TIMEOUT_FOR_IMPORT_OPTIMIZING_MS) {
OptimizeImportsProcessor(
project,
file
).run()
}
}
}
).show()
}
runFullFledgedMoveRefactoring(project, element, packageName, directory, targetFile, file)
return
}
val moveTarget = KotlinMoveTargetForDeferredFile(packageName, directory, null) {
val moveTarget = KotlinMoveTargetForDeferredFile(packageName, directory, targetFile = null) {
createKotlinFile(targetFileName, directory, packageName.asString())
}
@@ -120,22 +103,57 @@ class ExtractDeclarationFromCurrentFileIntention :
}
?: MoveSource(element)
val moveCallBack = MoveCallback {
val newFile = directory.findFile(targetFileName) as KtFile
val newDeclaration = newFile.declarations.first()
NavigationUtil.activateFileWithPsiElement(newFile)
FileEditorManager.getInstance(project).selectedTextEditor?.moveCaret(newDeclaration.startOffset + originalOffset)
runBlocking { withTimeoutOrNull(TIMEOUT_FOR_IMPORT_OPTIMIZING_MS) { OptimizeImportsProcessor(project, file).run() } }
}
val descriptor = MoveDeclarationsDescriptor(
project = project,
moveSource = moveSource,
moveTarget = moveTarget,
delegate = MoveDeclarationsDelegate.TopLevel,
project,
moveSource,
moveTarget,
MoveDeclarationsDelegate.TopLevel,
searchInCommentsAndStrings = false,
searchInNonCode = false,
moveCallback = MoveCallback {
val newFile = directory.findFile(targetFileName) as KtFile
val newDeclaration = newFile.declarations.first()
NavigationUtil.activateFileWithPsiElement(newFile)
FileEditorManager.getInstance(project).selectedTextEditor?.moveCaret(newDeclaration.startOffset + originalOffset)
runBlocking { withTimeoutOrNull(TIMEOUT_FOR_IMPORT_OPTIMIZING_MS) { OptimizeImportsProcessor(project, file).run() } }
}
moveCallback = moveCallBack
)
MoveKotlinDeclarationsProcessor(descriptor).run()
}
private fun runFullFledgedMoveRefactoring(
project: Project,
element: KtClassOrObject,
packageName: FqName,
directory: PsiDirectory,
targetFile: PsiFile?,
file: KtFile
) {
ApplicationManager.getApplication().invokeLater {
val callBack = MoveCallback {
runBlocking {
withTimeoutOrNull(TIMEOUT_FOR_IMPORT_OPTIMIZING_MS) {
OptimizeImportsProcessor(project, file).run()
}
}
}
MoveKotlinTopLevelDeclarationsDialog(
project,
setOf(element),
packageName.asString(),
directory,
targetFile as? KtFile,
/* moveToPackage = */ true,
/* searchInComments = */ true,
/* searchForTextOccurrences = */ true,
/* deleteEmptySourceFiles = */ true,
callBack
).showWithTransaction()
}
}
}