Skip to content

Commit

Permalink
Hint action to update usages on cut/paste of top-level declarations
Browse files Browse the repository at this point in the history
  • Loading branch information
valentinkip committed May 24, 2017
1 parent cb5459b commit 40bbf82
Show file tree
Hide file tree
Showing 31 changed files with 759 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)}")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ object KotlinStatisticsInfo {
startFromName = true
receiverAfterName = true
modifiers = emptySet()
renderDefaultValues = false
defaultParameterValueRenderer = null
parameterNameRenderingPolicy = ParameterNameRenderingPolicy.NONE
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions idea/src/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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"/>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -75,7 +75,7 @@ class ChangeMemberFunctionSignatureFix private constructor(
modifiers = emptySet()
classifierNamePolicy = ClassifierNamePolicy.SHORT
unitReturnType = false
renderDefaultValues = false
defaultParameterValueRenderer = null
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ class ReplaceProtectedToPublishedApiCallFix(

companion object : KotlinSingleIntentionActionFactory() {
val signatureRenderer = IdeDescriptorRenderers.SOURCE_CODE.withOptions {
renderDefaultValues = false
defaultParameterValueRenderer = null
startFromDeclarationKeyword = true
withoutReturnType = true
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}
}
}

Original file line number Diff line number Diff line change
@@ -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")
}
}
Original file line number Diff line number Diff line change
@@ -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
}
}
Loading

0 comments on commit 40bbf82

Please sign in to comment.