Move Declarations: Add intention which moves top-level declaration to the separate file in the same directory and package
#KT-4936 Fixed
This commit is contained in:
+13
@@ -0,0 +1,13 @@
|
||||
// File: foo/bar/myFile.kt
|
||||
package foo.bar2
|
||||
|
||||
fun test() {
|
||||
|
||||
}
|
||||
|
||||
// File: foo/bar/test.kt
|
||||
package foo.bar2
|
||||
|
||||
class Test {
|
||||
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// File: foo/bar/myFile.kt
|
||||
package foo.bar2
|
||||
|
||||
<spot>class Test</spot> {
|
||||
|
||||
}
|
||||
|
||||
fun test() {
|
||||
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<html>
|
||||
<body>
|
||||
This intention moves top-level class or object to the separate file in the same package and directory
|
||||
</body>
|
||||
</html>
|
||||
@@ -952,6 +952,11 @@
|
||||
<category>Kotlin</category>
|
||||
</intentionAction>
|
||||
|
||||
<intentionAction>
|
||||
<className>org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.MoveDeclarationToSeparateFileIntention</className>
|
||||
<category>Kotlin</category>
|
||||
</intentionAction>
|
||||
|
||||
<intentionAction>
|
||||
<className>org.jetbrains.kotlin.idea.intentions.ChangeVisibilityModifierIntention$Public</className>
|
||||
<category>Kotlin</category>
|
||||
|
||||
@@ -94,12 +94,12 @@ fun <T: Any> PsiElement.getAndRemoveCopyableUserData(key: Key<T>): T? {
|
||||
fun getOrCreateKotlinFile(fileName: String, targetDir: PsiDirectory): JetFile? =
|
||||
(targetDir.findFile(fileName) ?: createKotlinFile(fileName, targetDir)) as? JetFile
|
||||
|
||||
fun createKotlinFile(fileName: String, targetDir: PsiDirectory): JetFile {
|
||||
val packageName = targetDir.getPackage()?.getQualifiedName()
|
||||
|
||||
fun createKotlinFile(fileName: String,
|
||||
targetDir: PsiDirectory,
|
||||
packageName: String? = targetDir.getPackage()?.qualifiedName): JetFile {
|
||||
targetDir.checkCreateFile(fileName)
|
||||
val file = PsiFileFactory.getInstance(targetDir.getProject()).createFileFromText(
|
||||
fileName, JetFileType.INSTANCE, if (packageName != null && packageName.isNotEmpty()) "package $packageName \n\n" else ""
|
||||
fileName, JetFileType.INSTANCE, if (!packageName.isNullOrBlank()) "package $packageName \n\n" else ""
|
||||
)
|
||||
|
||||
return targetDir.add(file) as JetFile
|
||||
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* 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.refactoring.move.moveTopLevelDeclarations
|
||||
|
||||
import com.intellij.codeInsight.intention.LowPriorityAction
|
||||
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.util.TextRange
|
||||
import com.intellij.refactoring.move.MoveCallback
|
||||
import com.intellij.refactoring.util.CommonRefactoringUtil
|
||||
import org.jetbrains.kotlin.idea.core.refactoring.createKotlinFile
|
||||
import org.jetbrains.kotlin.idea.intentions.JetSelfTargetingRangeIntention
|
||||
import org.jetbrains.kotlin.idea.quickfix.moveCaret
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.ui.MoveKotlinTopLevelDeclarationsDialog
|
||||
import org.jetbrains.kotlin.lexer.JetTokens
|
||||
import org.jetbrains.kotlin.psi.JetClass
|
||||
import org.jetbrains.kotlin.psi.JetClassOrObject
|
||||
import org.jetbrains.kotlin.psi.JetFile
|
||||
import org.jetbrains.kotlin.psi.JetObjectDeclaration
|
||||
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
||||
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||
|
||||
public class MoveDeclarationToSeparateFileIntention :
|
||||
JetSelfTargetingRangeIntention<JetClassOrObject>(JetClassOrObject::class.java, "Move declaration to separate file"),
|
||||
LowPriorityAction {
|
||||
override fun applicabilityRange(element: JetClassOrObject): TextRange? {
|
||||
if (element.name == null) return null
|
||||
if (element.parent !is JetFile) return null
|
||||
if (element.hasModifier(JetTokens.PRIVATE_KEYWORD)) return null
|
||||
if (element.getContainingJetFile().declarations.size() == 1) return null
|
||||
|
||||
val keyword = when (element) {
|
||||
is JetClass -> element.getClassOrInterfaceKeyword()
|
||||
is JetObjectDeclaration -> element.getObjectKeyword()
|
||||
else -> return null
|
||||
}
|
||||
val startOffset = keyword?.startOffset ?: return null
|
||||
val endOffset = element.nameIdentifier?.endOffset ?: return null
|
||||
|
||||
text = "Move '${element.name}' to separate file"
|
||||
|
||||
return TextRange(startOffset, endOffset)
|
||||
}
|
||||
|
||||
override fun applyTo(element: JetClassOrObject, editor: Editor) {
|
||||
val file = element.getContainingJetFile()
|
||||
val project = file.project
|
||||
val originalOffset = editor.caretModel.offset - element.startOffset
|
||||
val directory = file.containingDirectory ?: return
|
||||
val packageName = file.packageFqName
|
||||
val targetFileName = "${element.name}.kt"
|
||||
val targetFile = directory.findFile(targetFileName)
|
||||
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? JetFile,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
null).show()
|
||||
}
|
||||
return
|
||||
}
|
||||
val moveTarget = DeferredJetFileKotlinMoveTarget(project, packageName) {
|
||||
createKotlinFile(targetFileName, directory, packageName.asString())
|
||||
}
|
||||
val moveOptions = MoveKotlinTopLevelDeclarationsOptions(
|
||||
elementsToMove = listOf(element),
|
||||
moveTarget = moveTarget,
|
||||
searchInCommentsAndStrings = false,
|
||||
searchInNonCode = false,
|
||||
updateInternalReferences = false,
|
||||
moveCallback = MoveCallback {
|
||||
val newFile = directory.findFile(targetFileName) as JetFile
|
||||
val newDeclaration = newFile.declarations.first()
|
||||
NavigationUtil.activateFileWithPsiElement(newFile)
|
||||
FileEditorManager.getInstance(project).selectedTextEditor?.moveCaret(newDeclaration.startOffset + originalOffset)
|
||||
}
|
||||
)
|
||||
MoveKotlinTopLevelDeclarationsProcessor(project, moveOptions).run()
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package source
|
||||
|
||||
fun bar() {
|
||||
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package p
|
||||
|
||||
class A {
|
||||
|
||||
}
|
||||
|
||||
fun foo() {
|
||||
|
||||
}
|
||||
|
||||
val x = 1
|
||||
+1
@@ -0,0 +1 @@
|
||||
File A.kt already exists
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package source
|
||||
|
||||
fun bar() {
|
||||
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package p
|
||||
|
||||
class <caret>A {
|
||||
|
||||
}
|
||||
|
||||
fun foo() {
|
||||
|
||||
}
|
||||
|
||||
val x = 1
|
||||
+1
@@ -0,0 +1 @@
|
||||
File A.kt already exists
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"mainFile": "source/test.kt",
|
||||
"intentionClass": "org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.MoveDeclarationToSeparateFileIntention"
|
||||
}
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
package p
|
||||
|
||||
class A {
|
||||
|
||||
}
|
||||
idea/testData/multiFileIntentions/moveDeclarationToSeparateFile/moveClassToFile/after/source/test.kt
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
package p
|
||||
|
||||
fun foo() {
|
||||
|
||||
}
|
||||
|
||||
val x = 1
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package p
|
||||
|
||||
class <caret>A {
|
||||
|
||||
}
|
||||
|
||||
fun foo() {
|
||||
|
||||
}
|
||||
|
||||
val x = 1
|
||||
idea/testData/multiFileIntentions/moveDeclarationToSeparateFile/moveClassToFile/moveClassToFile.test
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"mainFile": "source/test.kt",
|
||||
"intentionClass": "org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.MoveDeclarationToSeparateFileIntention"
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package p
|
||||
|
||||
class A {
|
||||
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package p
|
||||
|
||||
class <caret>A {
|
||||
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"mainFile": "source/test.kt",
|
||||
"intentionClass": "org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.MoveDeclarationToSeparateFileIntention",
|
||||
"isApplicable": "false"
|
||||
}
|
||||
@@ -18,25 +18,20 @@ package org.jetbrains.kotlin.idea.intentions
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonParser
|
||||
import com.intellij.codeInsight.TargetElementUtilBase
|
||||
import com.intellij.codeInsight.intention.IntentionAction
|
||||
import com.intellij.openapi.editor.Document
|
||||
import com.intellij.openapi.editor.EditorFactory
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.psi.PsiManager
|
||||
import com.intellij.refactoring.BaseRefactoringProcessor
|
||||
import com.intellij.util.PathUtil
|
||||
import com.intellij.refactoring.util.CommonRefactoringUtil
|
||||
import junit.framework.TestCase
|
||||
import org.jetbrains.kotlin.idea.jsonUtils.getNullableString
|
||||
import org.jetbrains.kotlin.idea.jsonUtils.getString
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.MoveAction
|
||||
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
|
||||
import org.jetbrains.kotlin.idea.test.KotlinMultiFileTestCase
|
||||
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
|
||||
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||
import org.jetbrains.kotlin.test.JetTestUtils
|
||||
import org.junit.Assert
|
||||
import java.io.File
|
||||
@@ -55,6 +50,7 @@ public abstract class AbstractMultiFileIntentionTest : KotlinMultiFileTestCase()
|
||||
|
||||
doTest({ rootDir, rootAfter ->
|
||||
val mainFile = rootDir.findFileByRelativePath(mainFilePath)!!
|
||||
val conflictFile = rootDir.findFileByRelativePath("$mainFilePath.conflicts")
|
||||
val document = FileDocumentManager.getInstance().getDocument(mainFile)!!
|
||||
val editor = EditorFactory.getInstance()!!.createEditor(document, getProject()!!)!!
|
||||
editor.getCaretModel().moveToOffset(extractCaretOffset(document))
|
||||
@@ -72,6 +68,11 @@ public abstract class AbstractMultiFileIntentionTest : KotlinMultiFileTestCase()
|
||||
intentionAction.invoke(getProject(), editor, mainPsiFile)
|
||||
}
|
||||
}
|
||||
|
||||
assert(conflictFile == null) { "Conflict file $conflictFile should not exist" }
|
||||
}
|
||||
catch (e: CommonRefactoringUtil.RefactoringErrorHintException) {
|
||||
JetTestUtils.assertEqualsToFile(File(conflictFile!!.path), e.getMessage()!!)
|
||||
}
|
||||
finally {
|
||||
PsiDocumentManager.getInstance(getProject()!!).commitAllDocuments()
|
||||
|
||||
@@ -35,6 +35,24 @@ public class MultiFileIntentionTestGenerated extends AbstractMultiFileIntentionT
|
||||
JetTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), new File("idea/testData/multiFileIntentions"), Pattern.compile("^(.+)\\.test$"));
|
||||
}
|
||||
|
||||
@TestMetadata("moveDeclarationToSeparateFile/moveClassToExistingFile/moveClassToExistingFile.test")
|
||||
public void testMoveDeclarationToSeparateFile_moveClassToExistingFile_MoveClassToExistingFile() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/multiFileIntentions/moveDeclarationToSeparateFile/moveClassToExistingFile/moveClassToExistingFile.test");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("moveDeclarationToSeparateFile/moveClassToFile/moveClassToFile.test")
|
||||
public void testMoveDeclarationToSeparateFile_moveClassToFile_MoveClassToFile() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/multiFileIntentions/moveDeclarationToSeparateFile/moveClassToFile/moveClassToFile.test");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("moveDeclarationToSeparateFile/moveSingleToFile/moveSingleToFile.test")
|
||||
public void testMoveDeclarationToSeparateFile_moveSingleToFile_MoveSingleToFile() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/multiFileIntentions/moveDeclarationToSeparateFile/moveSingleToFile/moveSingleToFile.test");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("moveFileToPackageMatchingDirectory/moveToDefaultDirectory/moveToDefaultDirectory.test")
|
||||
public void testMoveFileToPackageMatchingDirectory_moveToDefaultDirectory_MoveToDefaultDirectory() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/multiFileIntentions/moveFileToPackageMatchingDirectory/moveToDefaultDirectory/moveToDefaultDirectory.test");
|
||||
|
||||
Reference in New Issue
Block a user