Hint action to update usages on cut/paste of top-level declarations

This commit is contained in:
Valentin Kipyatkov
2017-03-19 16:51:25 +03:00
parent cb5459b7d7
commit 40bbf82a41
31 changed files with 759 additions and 10 deletions
@@ -206,7 +206,7 @@ interface DescriptorRendererOptions {
var renderCompanionObjectName: Boolean
var withoutSuperTypes: Boolean
var typeNormalizer: (KotlinType) -> KotlinType
var renderDefaultValues: Boolean
var defaultParameterValueRenderer: ((ValueParameterDescriptor) -> String)?
var secondaryConstructorsAsPrimary: Boolean
var renderAccessors: Boolean
var renderDefaultAnnotationArguments: Boolean
@@ -807,9 +807,9 @@ internal class DescriptorRendererImpl(
renderVariable(valueParameter, includeName, builder, topLevel)
val withDefaultValue = renderDefaultValues && (if (debugMode) valueParameter.declaresDefaultValue() else valueParameter.hasDefaultValue())
val withDefaultValue = defaultParameterValueRenderer != null && (if (debugMode) valueParameter.declaresDefaultValue() else valueParameter.hasDefaultValue())
if (withDefaultValue) {
builder.append(" = ...")
builder.append(" = ${defaultParameterValueRenderer!!(valueParameter)}")
}
}
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.renderer
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.types.KotlinType
import java.lang.IllegalStateException
@@ -47,7 +48,7 @@ internal class DescriptorRendererOptionsImpl : DescriptorRendererOptions {
this,
PropertyReference1Impl(DescriptorRendererOptionsImpl::class, field.name, "get" + field.name.capitalize())
)
field.set(copy, copy.property(value as Any))
field.set(copy, copy.property(value))
}
return copy
@@ -81,7 +82,7 @@ internal class DescriptorRendererOptionsImpl : DescriptorRendererOptions {
override var withoutTypeParameters by property(false)
override var withoutSuperTypes by property(false)
override var typeNormalizer by property<(KotlinType) -> KotlinType>({ it })
override var renderDefaultValues by property(true)
override var defaultParameterValueRenderer by property<((ValueParameterDescriptor) -> String)?>({ "..." })
override var secondaryConstructorsAsPrimary by property(true)
override var overrideRenderingPolicy by property(OverrideRenderingPolicy.RENDER_OPEN)
override var valueParametersHandler: DescriptorRenderer.ValueParametersHandler by property(DescriptorRenderer.ValueParametersHandler.DEFAULT)
@@ -834,6 +834,10 @@ fun main(args: Array<String>) {
model("copyPaste/imports", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doTestCut", testClassName = "Cut", recursive = false)
}
testClass<AbstractMoveOnCutPasteTest> {
model("copyPaste/moveDeclarations", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doTest")
}
testClass<AbstractHighlightExitPointsTest> {
model("exitPoints")
}
@@ -66,7 +66,7 @@ object KotlinStatisticsInfo {
startFromName = true
receiverAfterName = true
modifiers = emptySet()
renderDefaultValues = false
defaultParameterValueRenderer = null
parameterNameRenderingPolicy = ParameterNameRenderingPolicy.NONE
}
@@ -125,7 +125,7 @@ fun OverrideMemberChooserObject.generateMember(project: Project, copyDoc: Boolea
}
private val OVERRIDE_RENDERER = DescriptorRenderer.withOptions {
renderDefaultValues = false
defaultParameterValueRenderer = null
modifiers = setOf(DescriptorRendererModifier.OVERRIDE)
withDefinedIn = false
classifierNamePolicy = ClassifierNamePolicy.SOURCE_CODE_QUALIFIED
+5
View File
@@ -34,6 +34,10 @@
<component>
<implementation-class>org.jetbrains.kotlin.idea.highlighter.KotlinBeforeResolveHighlightingPass$Factory</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.refactoring.cutPaste.MoveDeclarationsPassFactory</implementation-class>
<skipForDefaultProject/>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.completion.LookupCancelWatcher</implementation-class>
</component>
@@ -582,6 +586,7 @@
<copyPastePostProcessor implementation="org.jetbrains.kotlin.idea.conversion.copy.ConvertTextJavaCopyPasteProcessor"/>
<copyPastePostProcessor implementation="org.jetbrains.kotlin.idea.codeInsight.KotlinCopyPasteReferenceProcessor"/>
<copyPastePreProcessor implementation="org.jetbrains.kotlin.idea.editor.KotlinLiteralCopyPasteProcessor"/>
<copyPastePostProcessor implementation="org.jetbrains.kotlin.idea.refactoring.cutPaste.MoveDeclarationsCopyPasteProcessor"/>
<breadcrumbsInfoProvider implementation="org.jetbrains.kotlin.idea.codeInsight.KotlinBreadcrumbsInfoProvider"/>
@@ -66,7 +66,7 @@ class ChangeMemberFunctionSignatureFix private constructor(
companion object {
private val SIGNATURE_SOURCE_RENDERER = IdeDescriptorRenderers.SOURCE_CODE.withOptions {
renderDefaultValues = false
defaultParameterValueRenderer = null
}
private val SIGNATURE_PREVIEW_RENDERER = DescriptorRenderer.withOptions {
@@ -75,7 +75,7 @@ class ChangeMemberFunctionSignatureFix private constructor(
modifiers = emptySet()
classifierNamePolicy = ClassifierNamePolicy.SHORT
unitReturnType = false
renderDefaultValues = false
defaultParameterValueRenderer = null
}
}
}
@@ -84,7 +84,7 @@ class ReplaceProtectedToPublishedApiCallFix(
companion object : KotlinSingleIntentionActionFactory() {
val signatureRenderer = IdeDescriptorRenderers.SOURCE_CODE.withOptions {
renderDefaultValues = false
defaultParameterValueRenderer = null
startFromDeclarationKeyword = true
withoutReturnType = true
}
@@ -0,0 +1,108 @@
/*
* Copyright 2010-2017 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.cutPaste
import com.intellij.codeInsight.editorActions.CopyPastePostProcessor
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.RangeMarker
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Ref
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiComment
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiWhiteSpace
import com.intellij.psi.util.PsiModificationTracker
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.psiUtil.elementsInRange
import java.awt.datatransfer.Transferable
class MoveDeclarationsCopyPasteProcessor : CopyPastePostProcessor<MoveDeclarationsTransferableData>() {
companion object {
private val LOG = Logger.getInstance(MoveDeclarationsCopyPasteProcessor::class.java)
fun rangeToDeclarations(file: KtFile, startOffset: Int, endOffset: Int): List<KtNamedDeclaration> {
val elementsInRange = file.elementsInRange(TextRange(startOffset, endOffset))
val meaningfulElements = elementsInRange.filterNot { it is PsiWhiteSpace || it is PsiComment }
if (meaningfulElements.isEmpty()) return emptyList()
if (!meaningfulElements.all { it is KtNamedDeclaration }) return emptyList()
@Suppress("UNCHECKED_CAST")
return meaningfulElements as List<KtNamedDeclaration>
}
}
override fun collectTransferableData(
file: PsiFile,
editor: Editor,
startOffsets: IntArray,
endOffsets: IntArray
): List<MoveDeclarationsTransferableData> {
if (file !is KtFile) return emptyList()
if (startOffsets.size != 1) return emptyList()
val declarations = rangeToDeclarations(file, startOffsets[0], endOffsets[0])
if (declarations.isEmpty() || declarations.any { it.parent !is KtFile }) return emptyList()
if (declarations.any { it.name == null }) return emptyList()
val declarationNames = declarations.map { it.name!! }.toSet()
val stubTexts = declarations.map { MoveDeclarationsTransferableData.STUB_RENDERER.render(it.resolveToDescriptor()) }
return listOf(MoveDeclarationsTransferableData(file.virtualFile.url, stubTexts, declarationNames))
}
override fun extractTransferableData(content: Transferable): List<MoveDeclarationsTransferableData> {
try {
if (content.isDataFlavorSupported(MoveDeclarationsTransferableData.DATA_FLAVOR)) {
return listOf(content.getTransferData(MoveDeclarationsTransferableData.DATA_FLAVOR) as MoveDeclarationsTransferableData)
}
}
catch (e: Throwable) {
LOG.error(e)
}
return emptyList()
}
override fun processTransferableData(
project: Project,
editor: Editor,
bounds: RangeMarker,
caretOffset: Int,
indented: Ref<Boolean>,
values: List<MoveDeclarationsTransferableData>
) {
val data = values.single()
fun putCookie() {
if (bounds.isValid) {
val cookie = MoveDeclarationsEditorCookie(data, bounds, PsiModificationTracker.SERVICE.getInstance(project).modificationCount)
editor.putUserData(MoveDeclarationsEditorCookie.KEY, cookie)
}
}
if (ApplicationManager.getApplication().isUnitTestMode) {
putCookie()
}
else {
// in real application we put cookie later to allow all other paste handlers do their work (because modificationCount will change)
ApplicationManager.getApplication().invokeLater(::putCookie)
}
}
}
@@ -0,0 +1,30 @@
/*
* Copyright 2010-2017 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.cutPaste
import com.intellij.openapi.editor.RangeMarker
import com.intellij.openapi.util.Key
class MoveDeclarationsEditorCookie(
val data: MoveDeclarationsTransferableData,
val bounds: RangeMarker,
val modificationCount: Long
) {
companion object {
val KEY = Key<MoveDeclarationsEditorCookie>("MoveDeclarationsEditorCookie")
}
}
@@ -0,0 +1,64 @@
/*
* Copyright 2010-2017 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.cutPaste
import com.intellij.codeInsight.hint.HintManager
import com.intellij.codeInspection.HintAction
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.RangeMarker
import com.intellij.openapi.keymap.KeymapUtil
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.util.PsiModificationTracker
import com.intellij.refactoring.BaseRefactoringIntentionAction
import org.jetbrains.kotlin.idea.conversion.copy.range
class MoveDeclarationsIntentionAction(
private val processor: MoveDeclarationsProcessor,
private val bounds: RangeMarker,
private val modificationCount: Long
) : BaseRefactoringIntentionAction(), HintAction {
override fun startInWriteAction() = false
override fun getText() = "Update usages to reflect package name change"
override fun getFamilyName() = "Update usages on declarations cut/paste"
override fun isAvailable(project: Project, editor: Editor, element: PsiElement): Boolean {
return PsiModificationTracker.SERVICE.getInstance(processor.project).modificationCount == modificationCount
}
override fun invoke(project: Project, editor: Editor, element: PsiElement) {
processor.performRefactoring()
}
override fun showHint(editor: Editor): Boolean {
val range = bounds.range ?: return false
if (editor.caretModel.offset != range.endOffset) return false
if (PsiModificationTracker.SERVICE.getInstance(processor.project).modificationCount != modificationCount) return false
val hintText = "$text? ${KeymapUtil.getFirstKeyboardShortcutText(ActionManager.getInstance().getAction(IdeActions.ACTION_SHOW_INTENTION_ACTIONS))}"
HintManager.getInstance().showQuestionHint(editor, hintText, range.endOffset, range.endOffset) {
processor.performRefactoring()
true
}
return true
}
}
@@ -0,0 +1,79 @@
/*
* Copyright 2010-2017 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.cutPaste
import com.intellij.codeHighlighting.Pass
import com.intellij.codeHighlighting.TextEditorHighlightingPass
import com.intellij.codeHighlighting.TextEditorHighlightingPassFactory
import com.intellij.codeHighlighting.TextEditorHighlightingPassRegistrar
import com.intellij.codeInsight.daemon.impl.HighlightInfo
import com.intellij.codeInsight.daemon.impl.HighlightInfoType
import com.intellij.codeInsight.daemon.impl.UpdateHighlightersUtil
import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixAction
import com.intellij.openapi.components.AbstractProjectComponent
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import com.intellij.psi.util.PsiModificationTracker
import org.jetbrains.kotlin.idea.conversion.copy.range
class MoveDeclarationsPassFactory(project: Project, highlightingPassRegistrar: TextEditorHighlightingPassRegistrar)
: AbstractProjectComponent(project), TextEditorHighlightingPassFactory {
init {
highlightingPassRegistrar.registerTextEditorHighlightingPass(this, TextEditorHighlightingPassRegistrar.Anchor.BEFORE, Pass.POPUP_HINTS, true, true)
}
override fun createHighlightingPass(file: PsiFile, editor: Editor): TextEditorHighlightingPass? {
return MyPass(file.project, file, editor)
}
private class MyPass(
private val project: Project,
private val file: PsiFile,
private val editor: Editor
) : TextEditorHighlightingPass(project, editor.document, true) {
override fun doCollectInformation(progress: ProgressIndicator) {}
override fun doApplyInformationToEditor() {
val info = buildHighlightingInfo()
UpdateHighlightersUtil.setHighlightersToEditor(project, myDocument!!, 0, file.textLength, listOfNotNull(info), colorsScheme, id)
}
private fun buildHighlightingInfo(): HighlightInfo? {
val cookie = editor.getUserData(MoveDeclarationsEditorCookie.KEY) ?: return null
if (cookie.modificationCount != PsiModificationTracker.SERVICE.getInstance(project).modificationCount) return null
val processor = MoveDeclarationsProcessor.build(editor, cookie)
if (processor == null) {
editor.putUserData(MoveDeclarationsEditorCookie.KEY, null)
return null
}
val info = HighlightInfo.newHighlightInfo(HighlightInfoType.INFORMATION)
.range(cookie.bounds.range!!)
.createUnconditionally()
QuickFixAction.registerQuickFixAction(info, MoveDeclarationsIntentionAction(processor, cookie.bounds, cookie.modificationCount))
return info
}
}
}
@@ -0,0 +1,134 @@
/*
* Copyright 2010-2017 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.cutPaste
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.RangeMarker
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.TextRange
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiManager
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.codeInsight.shorten.runRefactoringAndKeepDelayedRequests
import org.jetbrains.kotlin.idea.conversion.copy.range
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.*
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.idea.util.getSourceRoot
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.psiUtil.startOffset
class MoveDeclarationsProcessor(
val project: Project,
private val sourcePsiFile: KtFile,
private val targetPsiFile: KtFile,
private val pastedDeclarations: List<KtNamedDeclaration>,
private val stubTexts: List<String>
) {
companion object {
fun build(editor: Editor, cookie: MoveDeclarationsEditorCookie): MoveDeclarationsProcessor? {
val data = cookie.data
val project = editor.project ?: return null
val range = cookie.bounds.range ?: return null
val sourceFileUrl = data.sourceFileUrl
val sourceFile = VirtualFileManager.getInstance().findFileByUrl(sourceFileUrl) ?: return null
if (sourceFile.getSourceRoot(project) == null) return null
val psiDocumentManager = PsiDocumentManager.getInstance(project)
psiDocumentManager.commitAllDocuments()
val targetPsiFile = psiDocumentManager.getPsiFile(editor.document) as? KtFile ?: return null
if (targetPsiFile.virtualFile.getSourceRoot(project) == null) return null
val sourcePsiFile = PsiManager.getInstance(project).findFile(sourceFile) as? KtFile ?: return null
if (targetPsiFile == sourcePsiFile) return null
val declarations = MoveDeclarationsCopyPasteProcessor.rangeToDeclarations(targetPsiFile, range.startOffset, range.endOffset)
if (declarations.isEmpty() || declarations.any { it.parent !is KtFile }) return null
if (sourcePsiFile.packageFqName == targetPsiFile.packageFqName) return null
// check that declarations were cut (not copied)
val filteredDeclarations = sourcePsiFile.declarations.filter { it.name in data.declarationNames }
val stubs = data.stubTexts.toSet()
if (filteredDeclarations.any { MoveDeclarationsTransferableData.STUB_RENDERER.render(it.resolveToDescriptor()) in stubs }) return null
return MoveDeclarationsProcessor(project, sourcePsiFile, targetPsiFile, declarations, data.stubTexts)
}
}
private val psiDocumentManager = PsiDocumentManager.getInstance(project)
private val sourceDocument = psiDocumentManager.getDocument(sourcePsiFile)!!
fun performRefactoring() {
psiDocumentManager.commitAllDocuments()
val commandName = "Usage update"
val commandGroupId = Any() // we need to group both commands for undo
val insertedRange = project.executeWriteCommand<RangeMarker>(commandName, commandGroupId) {
//TODO: can stub declarations interfere with pasted declarations? I could not find such cases
insertStubDeclarations()
}
psiDocumentManager.commitDocument(sourceDocument)
val stubDeclarations = MoveDeclarationsCopyPasteProcessor.rangeToDeclarations(sourcePsiFile, insertedRange.startOffset, insertedRange.endOffset)
assert(stubDeclarations.size == pastedDeclarations.size) //TODO: can they ever differ?
val mover = object: Mover {
override fun invoke(declaration: KtNamedDeclaration, targetContainer: KtElement): KtNamedDeclaration {
val index = stubDeclarations.indexOf(declaration)
assert(index >= 0)
declaration.delete()
return pastedDeclarations[index]
}
}
val declarationProcessor = MoveKotlinDeclarationsProcessor(
MoveDeclarationsDescriptor(
elementsToMove = stubDeclarations,
moveTarget = KotlinMoveTargetForExistingElement(targetPsiFile),
delegate = MoveDeclarationsDelegate.TopLevel,
project = project
),
mover
)
val declarationUsages = declarationProcessor.findUsages().toList()
// val changeInfo = ContainerChangeInfo(ContainerInfo.Package(sourcePackageName), ContainerInfo.Package(targetPackageName))
// val internalUsages = file.getInternalReferencesToUpdateOnPackageNameChange(changeInfo)
project.executeWriteCommand(commandName, commandGroupId) {
// postProcessMoveUsages(internalUsages) //TODO?
project.runRefactoringAndKeepDelayedRequests { declarationProcessor.execute(declarationUsages) }
psiDocumentManager.doPostponedOperationsAndUnblockDocument(sourceDocument)
assert(insertedRange.isValid)
sourceDocument.deleteString(insertedRange.startOffset, insertedRange.endOffset)
}
}
private fun insertStubDeclarations(): RangeMarker {
val insertionOffset = sourcePsiFile.declarations.firstOrNull()?.startOffset ?: sourcePsiFile.textLength
val textToInsert = "\n//start\n\n" + stubTexts.joinToString(separator = "\n") + "\n//end\n"
sourceDocument.insertString(insertionOffset, textToInsert)
return sourceDocument.createRangeMarker(TextRange(insertionOffset, insertionOffset + textToInsert.length))
}
}
@@ -0,0 +1,43 @@
/*
* Copyright 2010-2017 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.cutPaste
import com.intellij.codeInsight.editorActions.TextBlockTransferableData
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import java.awt.datatransfer.DataFlavor
//TODO: how to make it work inside same project only?
class MoveDeclarationsTransferableData(
val sourceFileUrl: String,
val stubTexts: List<String>,
val declarationNames: Set<String>
) : TextBlockTransferableData {
override fun getFlavor() = DATA_FLAVOR
override fun getOffsetCount() = 0
override fun getOffsets(offsets: IntArray?, index: Int) = index
override fun setOffsets(offsets: IntArray?, index: Int) = index
companion object {
val DATA_FLAVOR = DataFlavor(MoveDeclarationsCopyPasteProcessor::class.java, "class: MoveDeclarationsCopyPasteProcessor")
val STUB_RENDERER = IdeDescriptorRenderers.SOURCE_CODE.withOptions {
defaultParameterValueRenderer = { "xxx" } // we need default value to be parsed as expression
}
}
}
@@ -0,0 +1,11 @@
import source.*
import target.bar
import target.foo
fun f() {
foo()
g(bar)
sourcePackFun()
}
fun g(p: Int){}
@@ -0,0 +1,9 @@
import source.*
fun f() {
foo()
g(bar)
sourcePackFun()
}
fun g(p: Int){}
@@ -0,0 +1,7 @@
package source
import target.targetPackFun
fun sourcePackFun(){}
@@ -0,0 +1,20 @@
package source
import target.targetPackFun
fun sourcePackFun(){}
<selection>
/* comment 1 */
fun foo() {
sourcePackFun()
targetPackFun()
}
/* comment 2 */
val bar = 10
/* comment 3 */
</selection>
@@ -0,0 +1,19 @@
package target
import source.sourcePackFun
fun targetPackFun(){}
/* comment 1 */
fun foo() {
sourcePackFun()
targetPackFun()
}
/* comment 2 */
val bar = 10
/* comment 3 */
@@ -0,0 +1,5 @@
package target
fun targetPackFun(){}
<caret>
+8
View File
@@ -0,0 +1,8 @@
// COPY
// IS_AVAILABLE: false
package source
<selection>
fun foo() {
}
</selection>
@@ -0,0 +1,7 @@
package dependency
class Dependency
fun f() {
to.foo(Dependency())
}
@@ -0,0 +1,9 @@
package dependency
import source.*
class Dependency
fun f() {
foo(Dependency())
}
@@ -0,0 +1,6 @@
// OPTIMIZE_IMPORTS_AFTER_CUT
package source
fun foo(o: Any) {
}
@@ -0,0 +1,12 @@
// OPTIMIZE_IMPORTS_AFTER_CUT
package source
import dependency.Dependency
<selection>
fun foo(dependency: Dependency, o: Any? = null) {
}
</selection>
fun foo(o: Any) {
}
@@ -0,0 +1,7 @@
package to
import dependency.Dependency
fun foo(dependency: Dependency, o: Any? = null) {
}
@@ -0,0 +1,7 @@
// IS_AVAILABLE: false
package source
<selection>
fun foo() {
}
</selection>
@@ -0,0 +1,3 @@
package source
<caret>
@@ -0,0 +1,89 @@
/*
* Copyright 2010-2017 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.codeInsight
import com.intellij.codeInsight.actions.OptimizeImportsProcessor
import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.openapi.util.io.FileUtil
import com.intellij.psi.PsiDocumentManager
import junit.framework.TestCase
import org.jetbrains.kotlin.idea.AbstractCopyPasteTest
import org.jetbrains.kotlin.idea.refactoring.cutPaste.MoveDeclarationsEditorCookie
import org.jetbrains.kotlin.idea.refactoring.cutPaste.MoveDeclarationsIntentionAction
import org.jetbrains.kotlin.idea.refactoring.cutPaste.MoveDeclarationsProcessor
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
import org.jetbrains.kotlin.idea.test.dumpTextWithErrors
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.test.KotlinTestUtils
import java.io.File
abstract class AbstractMoveOnCutPasteTest : AbstractCopyPasteTest() {
private val BASE_PATH = PluginTestCaseBase.getTestDataPathBase() + "/copyPaste/moveDeclarations"
private val OPTIMIZE_IMPORTS_AFTER_CUT_DIRECTIVE = "// OPTIMIZE_IMPORTS_AFTER_CUT"
private val IS_AVAILABLE_DIRECTIVE = "// IS_AVAILABLE:"
private val COPY_DIRECTIVE = "// COPY"
override fun getTestDataPath() = BASE_PATH
protected fun doTest(sourceFilePath: String) {
myFixture.testDataPath = BASE_PATH
val testFile = File(sourceFilePath)
val sourceFileName = testFile.name
val testFileText = FileUtil.loadFile(testFile, true)
val dependencyFileName = sourceFileName.replace(".kt", ".dependency.kt")
val dependencyPsiFile = configureByDependencyIfExists(dependencyFileName) as KtFile?
val sourcePsiFile = myFixture.configureByFile(sourceFileName) as KtFile
val useCopy = InTextDirectivesUtils.isDirectiveDefined(testFileText, COPY_DIRECTIVE)
myFixture.performEditorAction(if (useCopy) IdeActions.ACTION_COPY else IdeActions.ACTION_CUT)
PsiDocumentManager.getInstance(project).commitAllDocuments()
if (InTextDirectivesUtils.isDirectiveDefined(testFileText, OPTIMIZE_IMPORTS_AFTER_CUT_DIRECTIVE)) {
OptimizeImportsProcessor(project, sourcePsiFile).run()
}
editor.putUserData(MoveDeclarationsEditorCookie.KEY, null) // because editor is reused
val targetFileName = sourceFileName.replace(".kt", ".to.kt")
val targetPsiFile = configureTargetFile(targetFileName)
performNotWriteEditorAction(IdeActions.ACTION_PASTE)
val shouldBeAvailable = InTextDirectivesUtils.getPrefixedBoolean(testFileText, IS_AVAILABLE_DIRECTIVE) ?: true
val cookie = editor.getUserData(MoveDeclarationsEditorCookie.KEY)
val processor = cookie?.let { MoveDeclarationsProcessor.build(editor, cookie) }
TestCase.assertEquals(shouldBeAvailable, processor != null)
if (processor != null) {
processor.performRefactoring()
PsiDocumentManager.getInstance(project).commitAllDocuments()
if (dependencyPsiFile != null) {
KotlinTestUtils.assertEqualsToFile(File(BASE_PATH, dependencyFileName.replace(".kt", ".expected.kt")),
dependencyPsiFile.dumpTextWithErrors())
}
KotlinTestUtils.assertEqualsToFile(File(BASE_PATH, sourceFileName.replace(".kt", ".expected.kt")),
sourcePsiFile.dumpTextWithErrors())
KotlinTestUtils.assertEqualsToFile(File(BASE_PATH, targetFileName.replace(".kt", ".expected.kt")),
targetPsiFile.dumpTextWithErrors())
}
}
}
@@ -0,0 +1,62 @@
/*
* Copyright 2010-2017 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.codeInsight;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TargetBackend;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("idea/testData/copyPaste/moveDeclarations")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class MoveOnCutPasteTestGenerated extends AbstractMoveOnCutPasteTest {
public void testAllFilesPresentInMoveDeclarations() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/copyPaste/moveDeclarations"), Pattern.compile("^([^.]+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("ChangePackage.kt")
public void testChangePackage() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/copyPaste/moveDeclarations/ChangePackage.kt");
doTest(fileName);
}
@TestMetadata("Copy.kt")
public void testCopy() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/copyPaste/moveDeclarations/Copy.kt");
doTest(fileName);
}
@TestMetadata("OptimizeImportsAfterCut.kt")
public void testOptimizeImportsAfterCut() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/copyPaste/moveDeclarations/OptimizeImportsAfterCut.kt");
doTest(fileName);
}
@TestMetadata("SamePackage.kt")
public void testSamePackage() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/copyPaste/moveDeclarations/SamePackage.kt");
doTest(fileName);
}
}