diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/statistics/MoveRefactoringFUSCollector.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/statistics/MoveRefactoringFUSCollector.kt new file mode 100644 index 00000000000..93aae2dd099 --- /dev/null +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/statistics/MoveRefactoringFUSCollector.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.statistics + +object MoveRefactoringFUSCollector { + + /** + * @param isDefault is something changed in Move Refactoring Dialog check-boxes state + */ + fun log( + timeStarted: Long, + timeFinished: Long, + numberOfEntities: Int, + entity: MovedEntity, + destination: MoveRefactoringDestination, + isDefault: Boolean, + isSucceeded: Boolean + ) { + + val data = mapOf( + "lagging" to (timeFinished - timeStarted).toString(), + "entity" to entity.toString(), + "destination" to destination.toString(), + "number_of_entities" to numberOfEntities.toString(), + "are_settings_changed" to isDefault.toString(), + "succeeded" to isSucceeded.toString() + ) + + KotlinFUSLogger.log(FUSEventGroups.Refactoring, "Move", data) + } + + enum class MoveRefactoringDestination { + PACKAGE, FILE, DECLARATION + } + + enum class MovedEntity { + FUNCTIONS, CLASSES, MIXED, MPPCLASSES, MPPFUNCTIONS, MPPMIXED, PACKAGE, FILES + } +} \ No newline at end of file diff --git a/idea/resources/META-INF/plugin.xml b/idea/resources/META-INF/plugin.xml index 98c0c50098b..57226e59432 100644 --- a/idea/resources/META-INF/plugin.xml +++ b/idea/resources/META-INF/plugin.xml @@ -95,7 +95,7 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. - + diff --git a/idea/resources/META-INF/plugin.xml.191 b/idea/resources/META-INF/plugin.xml.191 index b757d535d62..7e9040a92c4 100644 --- a/idea/resources/META-INF/plugin.xml.191 +++ b/idea/resources/META-INF/plugin.xml.191 @@ -108,7 +108,7 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. - + diff --git a/idea/resources/META-INF/plugin.xml.192 b/idea/resources/META-INF/plugin.xml.192 index 4753e37ad2e..af25b49a568 100644 --- a/idea/resources/META-INF/plugin.xml.192 +++ b/idea/resources/META-INF/plugin.xml.192 @@ -108,7 +108,7 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. - + diff --git a/idea/src/org/jetbrains/kotlin/idea/actions/internal/refactoringTesting/cases/MoveKotlinDeclarationsHandlerTestActions.kt b/idea/src/org/jetbrains/kotlin/idea/actions/internal/refactoringTesting/cases/MoveKotlinDeclarationsHandlerTestActions.kt index bc5b89c41ed..e5f738e0a24 100644 --- a/idea/src/org/jetbrains/kotlin/idea/actions/internal/refactoringTesting/cases/MoveKotlinDeclarationsHandlerTestActions.kt +++ b/idea/src/org/jetbrains/kotlin/idea/actions/internal/refactoringTesting/cases/MoveKotlinDeclarationsHandlerTestActions.kt @@ -104,7 +104,7 @@ internal class MoveKotlinDeclarationsHandlerTestActions(private val caseDataKeep caseDataKeeper.caseData = model.testDataString() - model.computeModelResult(throwOnConflicts = true).run() + model.computeModelResult(throwOnConflicts = true).processor.run() } private fun doWithMoveKotlinNestedClassesToUpperLevelModel(nestedClass: KtClassOrObject, targetContainer: PsiElement?) { @@ -135,7 +135,7 @@ internal class MoveKotlinDeclarationsHandlerTestActions(private val caseDataKeep caseDataKeeper.caseData = model.testDataString() - model.computeModelResult(throwOnConflicts = true).run() + model.computeModelResult(throwOnConflicts = true).processor.run() } override fun invokeMoveKotlinNestedClassesRefactoring( @@ -188,7 +188,7 @@ internal class MoveKotlinDeclarationsHandlerTestActions(private val caseDataKeep caseDataKeeper.caseData = model.testDataString() - model.computeModelResult(throwOnConflicts = true).run() + model.computeModelResult(throwOnConflicts = true).processor.run() } override fun invokeKotlinSelectNestedClassChooser(nestedClass: KtClassOrObject, targetContainer: PsiElement?) { @@ -236,7 +236,7 @@ internal class MoveKotlinDeclarationsHandlerTestActions(private val caseDataKeep caseDataKeeper.caseData = model.testDataString() project.executeCommand(MoveHandler.REFACTORING_NAME) { - model.computeModelResult().run() + model.computeModelResult().processor.run() } } } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/Interfaces.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/Interfaces.kt index 0d005b21b83..da5a288dae3 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/Interfaces.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/Interfaces.kt @@ -6,11 +6,21 @@ package org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui import com.intellij.openapi.options.ConfigurationException +import com.intellij.refactoring.BaseRefactoringProcessor +import org.jetbrains.kotlin.idea.statistics.MoveRefactoringFUSCollector.MovedEntity +import org.jetbrains.kotlin.idea.statistics.MoveRefactoringFUSCollector.MoveRefactoringDestination -internal interface Model { +internal class ModelResultWithFUSData( + val processor: BaseRefactoringProcessor, + val elementsCount: Int, + val entityToMove: MovedEntity, + val destination: MoveRefactoringDestination +) + +internal interface Model { @Throws(ConfigurationException::class) - fun computeModelResult(throwOnConflicts: Boolean = false): T + fun computeModelResult(throwOnConflicts: Boolean = false): ModelResultWithFUSData @Throws(ConfigurationException::class) - fun computeModelResult(): T + fun computeModelResult(): ModelResultWithFUSData } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/KotlinAwareMoveFilesOrDirectoriesDialog.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/KotlinAwareMoveFilesOrDirectoriesDialog.kt index e731477cbc2..4f1737e4f08 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/KotlinAwareMoveFilesOrDirectoriesDialog.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/KotlinAwareMoveFilesOrDirectoriesDialog.kt @@ -18,7 +18,10 @@ import com.intellij.openapi.ui.DialogWrapper import com.intellij.openapi.ui.TextComponentAccessor import com.intellij.openapi.util.Disposer import com.intellij.openapi.vfs.LocalFileSystem -import com.intellij.psi.* +import com.intellij.psi.PsiDirectory +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiFileSystemItem +import com.intellij.psi.PsiManager import com.intellij.refactoring.RefactoringBundle import com.intellij.refactoring.copy.CopyFilesOrDirectoriesDialog import com.intellij.refactoring.copy.CopyFilesOrDirectoriesHandler @@ -34,9 +37,10 @@ import org.jetbrains.kotlin.idea.KotlinBundle import org.jetbrains.kotlin.idea.core.packageMatchesDirectoryOrImplicit import org.jetbrains.kotlin.idea.core.util.onTextChange import org.jetbrains.kotlin.idea.refactoring.isInKotlinAwareSourceRoot -import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.KotlinAwareMoveFilesOrDirectoriesProcessor +import org.jetbrains.kotlin.idea.refactoring.move.logFusForMoveRefactoring import org.jetbrains.kotlin.idea.util.application.executeCommand import org.jetbrains.kotlin.psi.KtFile +import java.util.* import javax.swing.JComponent class KotlinAwareMoveFilesOrDirectoriesDialog( @@ -69,6 +73,7 @@ class KotlinAwareMoveFilesOrDirectoriesDialog( } private val openInEditorCb = NonFocusableCheckBox(KotlinBundle.message("checkbox.text.open.moved.files.in.editor")) private val updatePackageDirectiveCb = NonFocusableCheckBox() + private val initializedCheckBoxesState: BitSet override fun getHelpId() = HELP_ID @@ -76,6 +81,14 @@ class KotlinAwareMoveFilesOrDirectoriesDialog( title = RefactoringBundle.message("move.title") init() initializeData() + + initializedCheckBoxesState = getCheckboxesState(applyDefaults = true) + } + + private fun getCheckboxesState(applyDefaults: Boolean) = BitSet(3).apply { + set(0, applyDefaults || searchReferencesCb.isSelected) //searchReferencesCb is true by default + set(1, !applyDefaults && openInEditorCb.isSelected) //openInEditorCb is false by default + set(2, updatePackageDirectiveCb.isSelected) } override fun createActions() = arrayOf(okAction, cancelAction, helpAction) @@ -163,7 +176,7 @@ class KotlinAwareMoveFilesOrDirectoriesDialog( } } - private fun getModel(): Model { + private fun getModel(): Model { val directory = LocalFileSystem.getInstance().findFileByPath(selectedDirectoryName)?.let { PsiManager.getInstance(project).findDirectory(it) @@ -188,20 +201,28 @@ class KotlinAwareMoveFilesOrDirectoriesDialog( override fun doOKAction() { - val processor: KotlinAwareMoveFilesOrDirectoriesProcessor + val modelResult: ModelResultWithFUSData try { - processor = getModel().computeModelResult() + modelResult = getModel().computeModelResult() } catch (e: ConfigurationException) { setErrorText(e.message) return } project.executeCommand(MoveHandler.REFACTORING_NAME) { - processor.run() + with(modelResult) { + logFusForMoveRefactoring( + elementsCount, + entityToMove, + destination, + getCheckboxesState(applyDefaults = false) == initializedCheckBoxesState, + Runnable { processor.run() } + ) + } } - setConfigurationValue(id = MOVE_FILES_OPEN_IN_EDITOR, value = openInEditorCb.isSelected, defaultValue = false) setConfigurationValue(id = MOVE_FILES_SEARCH_REFERENCES, value = searchReferencesCb.isSelected, defaultValue = true) + setConfigurationValue(id = MOVE_FILES_OPEN_IN_EDITOR, value = openInEditorCb.isSelected, defaultValue = false) RecentsManager.getInstance(project).registerRecentEntry(RECENT_KEYS, targetDirectoryField.childComponent.text) diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/KotlinAwareMoveFilesOrDirectoriesModel.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/KotlinAwareMoveFilesOrDirectoriesModel.kt index c78412b7d41..f03abaa10f9 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/KotlinAwareMoveFilesOrDirectoriesModel.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/KotlinAwareMoveFilesOrDirectoriesModel.kt @@ -20,19 +20,20 @@ import org.jetbrains.kotlin.idea.refactoring.isInKotlinAwareSourceRoot import org.jetbrains.kotlin.idea.refactoring.move.getOrCreateDirectory import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.KotlinAwareMoveFilesOrDirectoriesProcessor import org.jetbrains.kotlin.idea.refactoring.move.updatePackageDirective +import org.jetbrains.kotlin.idea.statistics.MoveRefactoringFUSCollector import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.psi.KtFile import java.nio.file.InvalidPathException import java.nio.file.Paths -class KotlinAwareMoveFilesOrDirectoriesModel( +internal class KotlinAwareMoveFilesOrDirectoriesModel( val project: Project, val elementsToMove: List, val targetDirectoryName: String, val updatePackageDirective: Boolean, val searchReferences: Boolean, val moveCallback: MoveCallback? -) : Model { +) : Model { private fun checkedGetElementsToMove(selectedDirectory: PsiDirectory): List { @@ -94,20 +95,29 @@ class KotlinAwareMoveFilesOrDirectoriesModel( } @Throws(ConfigurationException::class) - override fun computeModelResult(throwOnConflicts: Boolean): KotlinAwareMoveFilesOrDirectoriesProcessor { + override fun computeModelResult(throwOnConflicts: Boolean): ModelResultWithFUSData { checkModel() val selectedDir = checkedGetTargetDirectory() - return KotlinAwareMoveFilesOrDirectoriesProcessor( + val elementsToMove = checkedGetElementsToMove(selectedDir) + + val processor = KotlinAwareMoveFilesOrDirectoriesProcessor( project, - checkedGetElementsToMove(selectedDir), + elementsToMove, selectedDir, searchReferences = searchReferences, searchInComments = false, searchInNonJavaFiles = false, moveCallback = moveCallback ) + + return ModelResultWithFUSData( + processor, + elementsToMove.size, + MoveRefactoringFUSCollector.MovedEntity.FILES, + MoveRefactoringFUSCollector.MoveRefactoringDestination.PACKAGE + ) } } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinNestedClassesDialog.java b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinNestedClassesDialog.java index 728c22f3610..b47744a7fb5 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinNestedClassesDialog.java +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinNestedClassesDialog.java @@ -34,7 +34,7 @@ import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringUtilKt; import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo; import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberSelectionPanel; import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberSelectionTable; -import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.*; +import org.jetbrains.kotlin.idea.refactoring.move.MoveUtilsKt; import org.jetbrains.kotlin.idea.refactoring.ui.KotlinTypeReferenceEditorComboWithBrowseButton; import org.jetbrains.kotlin.psi.*; import javax.swing.*; @@ -82,6 +82,13 @@ public class MoveKotlinNestedClassesDialog extends RefactoringDialog { validateButtons(); myHelpAction.setEnabled(false); + + initializedCheckBoxesState = getCheckboxesState(); + } + + private final int initializedCheckBoxesState; + private int getCheckboxesState() { + return openInEditorCheckBox.isSelected() ? 1 : 0; } private void initClassChooser(@NotNull KtClassOrObject initialTargetClass) { @@ -217,7 +224,7 @@ public class MoveKotlinNestedClassesDialog extends RefactoringDialog { return "#" + getClass().getName(); } - private Model getModel() { + private Model getModel() { return new MoveKotlinNestedClassesModel( myProject, openInEditorCheckBox.isSelected(), @@ -230,16 +237,22 @@ public class MoveKotlinNestedClassesDialog extends RefactoringDialog { @Override protected void doAction() { - MoveKotlinDeclarationsProcessor processor; + ModelResultWithFUSData modelResult; try { - processor = getModel().computeModelResult(); + modelResult = getModel().computeModelResult(); } catch (ConfigurationException e) { setErrorText(e.getMessage()); return; } - invokeRefactoring(processor); + MoveUtilsKt.logFusForMoveRefactoring( + modelResult.getElementsCount(), + modelResult.getEntityToMove(), + modelResult.getDestination(), + getCheckboxesState() == initializedCheckBoxesState, + () -> invokeRefactoring(modelResult.getProcessor()) + ); } @Override diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinNestedClassesModel.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinNestedClassesModel.kt index 809c2bff4c3..483c100a27e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinNestedClassesModel.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinNestedClassesModel.kt @@ -13,6 +13,8 @@ import com.intellij.refactoring.RefactoringBundle import com.intellij.refactoring.move.MoveCallback import org.jetbrains.kotlin.idea.KotlinBundle import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.* +import org.jetbrains.kotlin.idea.statistics.MoveRefactoringFUSCollector.MoveRefactoringDestination +import org.jetbrains.kotlin.idea.statistics.MoveRefactoringFUSCollector.MovedEntity import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtElement @@ -23,7 +25,7 @@ internal class MoveKotlinNestedClassesModel( val originalClass: KtClassOrObject, val targetClass: PsiElement?, val moveCallback: MoveCallback? -) : Model { +) : Model { private fun getCheckedTargetClass(): KtElement { val targetClass = this.targetClass ?: throw ConfigurationException(RefactoringBundle.message("no.destination.class.specified")) @@ -51,7 +53,7 @@ internal class MoveKotlinNestedClassesModel( override fun computeModelResult() = computeModelResult(throwOnConflicts = false) @Throws(ConfigurationException::class) - override fun computeModelResult(throwOnConflicts: Boolean): MoveKotlinDeclarationsProcessor { + override fun computeModelResult(throwOnConflicts: Boolean): ModelResultWithFUSData { val elementsToMove = selectedElementsToMove val target = KotlinMoveTargetForExistingElement(getCheckedTargetClass()) val delegate = MoveDeclarationsDelegate.NestedClass() @@ -67,6 +69,13 @@ internal class MoveKotlinNestedClassesModel( openInEditor = openInEditorCheckBox ) - return MoveKotlinDeclarationsProcessor(descriptor, Mover.Default, throwOnConflicts) + val processor = MoveKotlinDeclarationsProcessor(descriptor, Mover.Default, throwOnConflicts) + + return ModelResultWithFUSData( + processor, + elementsToMove.size, + MovedEntity.CLASSES, + MoveRefactoringDestination.DECLARATION + ) } } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinNestedClassesToUpperLevelDialog.java b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinNestedClassesToUpperLevelDialog.java index 0f3caee04a2..9b40f7f36cc 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinNestedClassesToUpperLevelDialog.java +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinNestedClassesToUpperLevelDialog.java @@ -31,7 +31,6 @@ import org.jetbrains.kotlin.idea.core.KotlinNameSuggester; import org.jetbrains.kotlin.idea.core.NewDeclarationNameValidator; import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringSettings; import org.jetbrains.kotlin.idea.refactoring.move.MoveUtilsKt; -import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.MoveKotlinDeclarationsProcessor; import org.jetbrains.kotlin.name.FqName; import org.jetbrains.kotlin.psi.KtClass; import org.jetbrains.kotlin.psi.KtClassBody; @@ -41,6 +40,7 @@ import org.jetbrains.kotlin.types.KotlinType; import javax.swing.*; import java.awt.*; +import java.util.BitSet; import java.util.Collections; import java.util.List; @@ -126,6 +126,19 @@ public class MoveKotlinNestedClassesToUpperLevelDialog extends MoveDialogBase { return ((ClassDescriptor) innerClassDescriptor.getContainingDeclaration()).getDefaultType(); } + private BitSet initializedCheckBoxesState; + private BitSet getCheckboxesState(boolean applyDefaults) { + + BitSet state = new BitSet(3); + + state.set(0, !applyDefaults && searchInCommentsCheckBox.isSelected()); //searchInCommentsCheckBox default is false + state.set(1, !applyDefaults && searchForTextOccurrencesCheckBox.isSelected()); //searchForTextOccurrencesCheckBox default is false + state.set(2, passOuterClassCheckBox.isSelected()); + + return state; + } + + @Override protected void init() { classNameField.setText(innerClass.getName()); @@ -182,6 +195,8 @@ public class MoveKotlinNestedClassesToUpperLevelDialog extends MoveDialogBase { searchInCommentsCheckBox.setSelected(settings.MOVE_TO_UPPER_LEVEL_SEARCH_IN_COMMENTS); super.init(); + + initializedCheckBoxesState = getCheckboxesState(true); } @Override @@ -233,7 +248,7 @@ public class MoveKotlinNestedClassesToUpperLevelDialog extends MoveDialogBase { } } - private Model getModel() { + private Model getModel() { return new MoveKotlinNestedClassesToUpperLevelModelWithUIChooser( project, innerClass, @@ -251,9 +266,9 @@ public class MoveKotlinNestedClassesToUpperLevelDialog extends MoveDialogBase { @Override protected void doAction() { - MoveKotlinDeclarationsProcessor processor; + ModelResultWithFUSData modelResult; try { - processor = getModel().computeModelResult(); + modelResult = getModel().computeModelResult(); } catch (ConfigurationException e) { setErrorText(e.getMessage()); @@ -266,6 +281,12 @@ public class MoveKotlinNestedClassesToUpperLevelDialog extends MoveDialogBase { saveOpenInEditorOption(); - invokeRefactoring(processor); + MoveUtilsKt.logFusForMoveRefactoring( + modelResult.getElementsCount(), + modelResult.getEntityToMove(), + modelResult.getDestination(), + getCheckboxesState(false).equals(initializedCheckBoxesState), + () -> invokeRefactoring(modelResult.getProcessor()) + ); } } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinNestedClassesToUpperLevelModel.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinNestedClassesToUpperLevelModel.kt index 210cfd8a005..ebfaa3efa3e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinNestedClassesToUpperLevelModel.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinNestedClassesToUpperLevelModel.kt @@ -28,6 +28,8 @@ import org.jetbrains.kotlin.idea.refactoring.createKotlinFile import org.jetbrains.kotlin.idea.refactoring.move.getTargetPackageFqName import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.* import org.jetbrains.kotlin.idea.roots.getSuitableDestinationSourceRoots +import org.jetbrains.kotlin.idea.statistics.MoveRefactoringFUSCollector.MovedEntity +import org.jetbrains.kotlin.idea.statistics.MoveRefactoringFUSCollector.MoveRefactoringDestination import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.KtClassOrObject @@ -48,7 +50,7 @@ internal abstract class MoveKotlinNestedClassesToUpperLevelModel( val isSearchInNonJavaFiles: Boolean, val packageName: String, val isOpenInEditor: Boolean -) : Model { +) : Model { protected abstract fun chooseSourceRoot( newPackage: PackageWrapper, @@ -157,9 +159,9 @@ internal abstract class MoveKotlinNestedClassesToUpperLevelModel( } @Throws(ConfigurationException::class) - private fun getMoveTarget(): KotlinMoveTarget { + private fun getMoveTarget(): Pair { val target = getTargetContainerWithValidation() - if (target is PsiDirectory) { + return if (target is PsiDirectory) { val targetPackageFqName = getTargetPackageFqName(target) ?: throw ConfigurationException(KotlinBundle.message("text.cannot.find.target.package.name")) @@ -169,13 +171,15 @@ internal abstract class MoveKotlinNestedClassesToUpperLevelModel( val targetFileName = suggestedName + "." + KotlinFileType.EXTENSION - return KotlinMoveTargetForDeferredFile( + val target = KotlinMoveTargetForDeferredFile( targetPackageFqName, target, targetFile = null ) { createKotlinFile(targetFileName, target, targetPackageFqName.asString()) } + + target to MoveRefactoringDestination.FILE } else { - return KotlinMoveTargetForExistingElement(target as KtElement) + KotlinMoveTargetForExistingElement(target as KtElement) to MoveRefactoringDestination.DECLARATION } } @@ -183,7 +187,7 @@ internal abstract class MoveKotlinNestedClassesToUpperLevelModel( override fun computeModelResult() = computeModelResult(throwOnConflicts = false) @Throws(ConfigurationException::class) - override fun computeModelResult(throwOnConflicts: Boolean): MoveKotlinDeclarationsProcessor { + override fun computeModelResult(throwOnConflicts: Boolean): ModelResultWithFUSData { val moveTarget = getMoveTarget() @@ -192,7 +196,7 @@ internal abstract class MoveKotlinNestedClassesToUpperLevelModel( val moveDescriptor = MoveDeclarationsDescriptor( project, MoveSource(innerClass), - moveTarget, + moveTarget.first, delegate, searchInComments, isSearchInNonJavaFiles, @@ -201,6 +205,13 @@ internal abstract class MoveKotlinNestedClassesToUpperLevelModel( openInEditor = isOpenInEditor ) - return MoveKotlinDeclarationsProcessor(moveDescriptor, Mover.Default, throwOnConflicts) + val processor = MoveKotlinDeclarationsProcessor(moveDescriptor, Mover.Default, throwOnConflicts) + + return ModelResultWithFUSData( + processor = processor, + elementsCount = 1, + entityToMove = MovedEntity.CLASSES, + destination = moveTarget.second + ) } } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinTopLevelDeclarationsDialog.java b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinTopLevelDeclarationsDialog.java index fa40fbb6463..e3861db0fea 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinTopLevelDeclarationsDialog.java +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinTopLevelDeclarationsDialog.java @@ -12,7 +12,6 @@ import com.intellij.openapi.ui.TextFieldWithBrowseButton; import com.intellij.openapi.util.Pass; import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiFile; -import com.intellij.refactoring.BaseRefactoringProcessor; import com.intellij.refactoring.RefactoringBundle; import com.intellij.refactoring.classMembers.AbstractMemberInfoModel; import com.intellij.refactoring.classMembers.MemberInfoBase; @@ -42,7 +41,6 @@ import org.jetbrains.kotlin.idea.refactoring.move.MoveUtilsKt; import org.jetbrains.kotlin.idea.refactoring.ui.KotlinDestinationFolderComboBox; import org.jetbrains.kotlin.idea.refactoring.ui.KotlinFileChooserDialog; import org.jetbrains.kotlin.idea.util.ExpectActualUtilKt; -import org.jetbrains.kotlin.psi.KtElement; import org.jetbrains.kotlin.psi.KtFile; import org.jetbrains.kotlin.psi.KtNamedDeclaration; import org.jetbrains.kotlin.psi.KtPureElement; @@ -51,10 +49,8 @@ import org.jetbrains.kotlin.psi.psiUtil.KtPsiUtilKt; import javax.swing.*; import java.awt.*; import java.io.File; -import java.util.Collection; +import java.util.*; import java.util.List; -import java.util.Map; -import java.util.Set; import static org.jetbrains.kotlin.idea.roots.ProjectRootUtilsKt.getSuitableDestinationSourceRoots; @@ -148,6 +144,24 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog { initMemberInfo(elementsToMove, sourceFiles); updateControls(); + + initializedCheckBoxesState = getCheckboxesState(true); + } + + private final BitSet initializedCheckBoxesState; + private BitSet getCheckboxesState(boolean applyDefaults) { + + BitSet state = new BitSet(7); + + state.set(0, applyDefaults || cbSearchInComments.isSelected()); //cbSearchInComments default is true + state.set(1, applyDefaults || cbSearchTextOccurrences.isSelected()); //cbSearchTextOccurrences default is true + state.set(2, applyDefaults || cbDeleteEmptySourceFiles.isSelected()); //cbDeleteEmptySourceFiles default is true + state.set(3, applyDefaults || cbApplyMPPDeclarationsMove.isSelected()); //cbApplyMPPDeclarationsMove default is true + state.set(4, cbSpecifyFileNameInPackage.isSelected()); + state.set(5, cbUpdatePackageDirective.isSelected()); + state.set(6, cbSearchReferences.isSelected()); + + return state; } private static List getSourceFiles(@NotNull Collection elementsToMove) { @@ -427,16 +441,18 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog { return elementsToMove; } - private Model getModel() throws ConfigurationException { + private MoveKotlinTopLevelDeclarationsModel getModel() throws ConfigurationException { DirectoryChooser.ItemWrapper selectedItem = (DirectoryChooser.ItemWrapper) destinationFolderCB.getComboBox().getSelectedItem(); PsiDirectory selectedPsiDirectory = selectedItem != null ? selectedItem.getDirectory() : initialTargetDirectory; boolean mppDeclarationSelected = cbApplyMPPDeclarationsMove.isSelected() && isMppDeclarationSelected(); + List selectedElements = getSelectedElementsToMoveChecked(); + return new MoveKotlinTopLevelDeclarationsModel( myProject, - getSelectedElementsToMoveChecked(), + selectedElements, getTargetPackage(), selectedPsiDirectory, tfFileNameInPackage.getText(), @@ -456,9 +472,9 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog { @Override protected void doAction() { - BaseRefactoringProcessor processor; + ModelResultWithFUSData modelResult; try { - processor = getModel().computeModelResult(); + modelResult = getModel().computeModelResult(); } catch (ConfigurationException e) { setErrorText(e.getMessage()); @@ -468,7 +484,13 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog { saveRefactoringSettings(); try { - invokeRefactoring(processor); + MoveUtilsKt.logFusForMoveRefactoring( + modelResult.getElementsCount(), + modelResult.getEntityToMove(), + modelResult.getDestination(), + getCheckboxesState(false).equals(initializedCheckBoxesState), + () -> invokeRefactoring(modelResult.getProcessor()) + ); } catch (IncorrectOperationException e) { CommonRefactoringUtil.showErrorMessage(RefactoringBundle.message("error.title"), e.getMessage(), null, myProject); } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinTopLevelDeclarationsModel.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinTopLevelDeclarationsModel.kt index 2bc330db392..3cf0a46c831 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinTopLevelDeclarationsModel.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinTopLevelDeclarationsModel.kt @@ -27,11 +27,15 @@ import org.jetbrains.kotlin.idea.refactoring.getOrCreateKotlinFile import org.jetbrains.kotlin.idea.refactoring.move.getOrCreateDirectory import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.* import org.jetbrains.kotlin.idea.refactoring.move.updatePackageDirective +import org.jetbrains.kotlin.idea.statistics.MoveRefactoringFUSCollector.MovedEntity +import org.jetbrains.kotlin.idea.statistics.MoveRefactoringFUSCollector.MoveRefactoringDestination import org.jetbrains.kotlin.idea.util.collectAllExpectAndActualDeclaration import org.jetbrains.kotlin.idea.util.isEffectivelyActual import org.jetbrains.kotlin.idea.util.isExpectDeclaration import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtFunction import org.jetbrains.kotlin.psi.KtNamedDeclaration import java.io.File import java.nio.file.InvalidPathException @@ -53,7 +57,7 @@ internal class MoveKotlinTopLevelDeclarationsModel( val isFullFileMove: Boolean, val applyMPPDeclarations: Boolean, val moveCallback: MoveCallback? -) : Model { +) : Model { private inline fun Set.mapToSingleOrNull(transform: (T) -> K?): K? = mapTo(mutableSetOf(), transform).singleOrNull() @@ -217,15 +221,29 @@ internal class MoveKotlinTopLevelDeclarationsModel( override fun computeModelResult() = computeModelResult(throwOnConflicts = false) @Throws(ConfigurationException::class) - override fun computeModelResult(throwOnConflicts: Boolean): BaseRefactoringProcessor { + override fun computeModelResult(throwOnConflicts: Boolean): ModelResultWithFUSData { verifyBeforeRun() + val classType = if (elementsToMoveHasMPP) MovedEntity.MPPCLASSES else MovedEntity.CLASSES + val functionType = if (elementsToMoveHasMPP) MovedEntity.MPPFUNCTIONS else MovedEntity.FUNCTIONS + val mixedType = if (elementsToMoveHasMPP) MovedEntity.MPPMIXED else MovedEntity.MIXED + + val allClasses = elementsToMove.any { element -> element is KtClassOrObject } + val allFunctions = elementsToMove.any { element -> element is KtFunction } + val entity = if (allClasses && allFunctions) mixedType else + if (allClasses) classType else functionType + + val destination = if (isMoveToPackage) MoveRefactoringDestination.PACKAGE else MoveRefactoringDestination.FILE + if (isFullFileMove && isMoveToPackage) { - tryMoveFile(throwOnConflicts)?.let { return it } + tryMoveFile(throwOnConflicts)?.let { + return ModelResultWithFUSData(it, elementsToMove.size, entity, destination) + } } - return moveDeclaration(throwOnConflicts) + val processor = moveDeclaration(throwOnConflicts) + return ModelResultWithFUSData(processor, elementsToMove.size, entity, destination) } private fun tryMoveFile(throwOnConflicts: Boolean): BaseRefactoringProcessor? { diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveFilesOrDirectories/KotlinMoveFilesOrDirectoriesHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveFilesOrDirectories/KotlinMoveFilesOrDirectoriesHandler.kt index 3fcec0099ad..c2182b301d9 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveFilesOrDirectories/KotlinMoveFilesOrDirectoriesHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveFilesOrDirectories/KotlinMoveFilesOrDirectoriesHandler.kt @@ -16,6 +16,7 @@ import com.intellij.refactoring.move.MoveHandler import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFilesOrDirectoriesUtil import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade import org.jetbrains.kotlin.idea.refactoring.move.MoveFilesOrDirectoriesHandlerCompat +import org.jetbrains.kotlin.idea.refactoring.move.logFusForMoveRefactoring import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui.KotlinAwareMoveFilesOrDirectoriesDialog import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui.KotlinAwareMoveFilesOrDirectoriesModel import org.jetbrains.kotlin.idea.util.application.executeCommand @@ -60,7 +61,8 @@ class KotlinMoveFilesOrDirectoriesHandler : MoveFilesOrDirectoriesHandlerCompat( val initialTargetDirectory = MoveFilesOrDirectoriesUtil.getInitialTargetDirectory(targetDirectory, elements) - if (ApplicationManager.getApplication().isUnitTestMode && initialTargetDirectory !== null) { + val isTestUnitMode = ApplicationManager.getApplication().isUnitTestMode + if (isTestUnitMode && initialTargetDirectory !== null) { KotlinAwareMoveFilesOrDirectoriesModel( project, adjustedElementsToMove, @@ -70,7 +72,19 @@ class KotlinMoveFilesOrDirectoriesHandler : MoveFilesOrDirectoriesHandlerCompat( moveCallback = callback ).run { project.executeCommand(MoveHandler.REFACTORING_NAME) { - computeModelResult().run() + with(computeModelResult()) { + if (!isTestUnitMode) { + logFusForMoveRefactoring( + elementsCount, + entityToMove, + destination, + true, + Runnable { processor.run() } + ) + } else { + processor.run() + } + } } } return diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveUtils.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveUtils.kt index 55c88bf5084..f0f350e9ac1 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveUtils.kt @@ -38,6 +38,7 @@ import org.jetbrains.kotlin.idea.refactoring.fqName.isImported import org.jetbrains.kotlin.idea.references.KtSimpleNameReference import org.jetbrains.kotlin.idea.references.KtSimpleNameReference.ShorteningMode import org.jetbrains.kotlin.idea.references.mainReference +import org.jetbrains.kotlin.idea.statistics.MoveRefactoringFUSCollector import org.jetbrains.kotlin.idea.util.application.executeCommand import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.load.java.descriptors.JavaCallableMemberDescriptor @@ -55,7 +56,9 @@ import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver import org.jetbrains.kotlin.types.expressions.DoubleColonLHS import org.jetbrains.kotlin.utils.addIfNotNull import java.io.File +import java.lang.System.currentTimeMillis import java.util.* +import javax.swing.JCheckBox sealed class ContainerInfo { abstract val fqName: FqName? @@ -679,4 +682,30 @@ internal fun getTargetPackageFqName(targetContainer: PsiElement): FqName? { return if (targetPackage != null) FqName(targetPackage.qualifiedName) else null } return if (targetContainer is KtFile) targetContainer.packageFqName else null +} + +internal fun logFusForMoveRefactoring( + numberOfEntities: Int, + entity: MoveRefactoringFUSCollector.MovedEntity, + destination: MoveRefactoringFUSCollector.MoveRefactoringDestination, + isDefault: Boolean, + body: Runnable +) { + val timeStarted = currentTimeMillis() + + var succeeded = false + try { + body.run() + succeeded = true + } finally { + MoveRefactoringFUSCollector.log( + timeStarted = timeStarted, + timeFinished = currentTimeMillis(), + numberOfEntities = numberOfEntities, + destination = destination, + isDefault = isDefault, + entity = entity, + isSucceeded = succeeded, + ) + } } \ No newline at end of file