Hint action to update usages on cut/paste of top-level declarations
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -84,7 +84,7 @@ class ReplaceProtectedToPublishedApiCallFix(
|
||||
|
||||
companion object : KotlinSingleIntentionActionFactory() {
|
||||
val signatureRenderer = IdeDescriptorRenderers.SOURCE_CODE.withOptions {
|
||||
renderDefaultValues = false
|
||||
defaultParameterValueRenderer = null
|
||||
startFromDeclarationKeyword = true
|
||||
withoutReturnType = true
|
||||
}
|
||||
|
||||
+108
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+30
@@ -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")
|
||||
}
|
||||
}
|
||||
+64
@@ -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
|
||||
}
|
||||
}
|
||||
+79
@@ -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))
|
||||
}
|
||||
}
|
||||
+43
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -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>
|
||||
@@ -0,0 +1,8 @@
|
||||
// COPY
|
||||
// IS_AVAILABLE: false
|
||||
package source
|
||||
|
||||
<selection>
|
||||
fun foo() {
|
||||
}
|
||||
</selection>
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package dependency
|
||||
|
||||
class Dependency
|
||||
|
||||
fun f() {
|
||||
to.foo(Dependency())
|
||||
}
|
||||
+9
@@ -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) {
|
||||
}
|
||||
+7
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user