diff --git a/platform/refactoring/resources/messages/RefactoringBundle.properties b/platform/refactoring/resources/messages/RefactoringBundle.properties
index 5c99771a573e..3d0289ce605c 100644
--- a/platform/refactoring/resources/messages/RefactoringBundle.properties
+++ b/platform/refactoring/resources/messages/RefactoringBundle.properties
@@ -48,6 +48,9 @@ visibility.combo.title=&Visibility:
moveInstanceMethod.select.an.instance.parameter=Select an &instance expression:
choose.destination.package=Choose Destination Package
create.explicit.package.warning=Implicit package ''{0}'' will become explicit
+move.added.kmp.suffixes.warning.0=Platform suffix ''.{0}'' will be copied across multiple source sets, consider removing it
+move.added.kmp.suffixes.info=Multiplatform file suffixes will be added automatically
+tooltip.text.move.kmp.target.file=Base file name in the common source set. Platform source sets will receive suffixes in accordance with the multiplatform conventions.
interface.name.prompt=&Interface name:
package.for.new.interface=&Package for new interface:
pull.up.members.to=P&ull up members of {0} to:
diff --git a/plugins/kotlin/base/resources/resources-en/messages/KotlinBundle.properties b/plugins/kotlin/base/resources/resources-en/messages/KotlinBundle.properties
index c30ac6d5962f..79fae9bc139b 100644
--- a/plugins/kotlin/base/resources/resources-en/messages/KotlinBundle.properties
+++ b/plugins/kotlin/base/resources/resources-en/messages/KotlinBundle.properties
@@ -889,8 +889,8 @@ parameter.name.is.invalid=Parameter name ''{0}'' is invalid
parameter.type.is.invalid=Parameter type ''{0}'' is invalid
parameter.types.are.not.denotable=Cannot extract method since following types are not denotable in the target scope:
refactoring.move.non.kotlin.file=Target must be a Kotlin file
-refactoring.class.destination=Class destination
-refactoring.file.destination=File destination
+refactoring.class.destination=Class destination:
+refactoring.file.destination=File destination:
refactoring.cannot.find.target.class=Cannot find target class
return.type.is.invalid=Return type is invalid
searching.usages.of.0.parameter=Searching usages of ''{0}''parameter
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/after/commonMain/commonMain.iml b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/after/commonMain/commonMain.iml
new file mode 100644
index 000000000000..7cd42c203d42
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/after/commonMain/commonMain.iml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/after/commonMain/src/bar/target.kt b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/after/commonMain/src/bar/target.kt
new file mode 100644
index 000000000000..bbc178233ae7
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/after/commonMain/src/bar/target.kt
@@ -0,0 +1,3 @@
+package bar
+
+expect fun foo(): Int
\ No newline at end of file
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/after/commonMain/src/foo/.keep b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/after/commonMain/src/foo/.keep
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/after/commonMain/src/foo/base.kt b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/after/commonMain/src/foo/base.kt
new file mode 100644
index 000000000000..bee4460472f2
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/after/commonMain/src/foo/base.kt
@@ -0,0 +1,4 @@
+package foo
+
+fun regularCommon() {
+}
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/after/jsMain/jsMain.iml b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/after/jsMain/jsMain.iml
new file mode 100644
index 000000000000..65b5735a6e4a
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/after/jsMain/jsMain.iml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ commonMain
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/after/jsMain/src/bar/target.js.kt b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/after/jsMain/src/bar/target.js.kt
new file mode 100644
index 000000000000..73303e977ddb
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/after/jsMain/src/bar/target.js.kt
@@ -0,0 +1,6 @@
+package bar
+
+fun existingInTargetPackageJs() {
+}
+
+actual fun foo(): Int = 1
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/after/jsMain/src/foo/.keep b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/after/jsMain/src/foo/.keep
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/after/jsMain/src/foo/base.js.kt b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/after/jsMain/src/foo/base.js.kt
new file mode 100644
index 000000000000..a3fdad49d202
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/after/jsMain/src/foo/base.js.kt
@@ -0,0 +1,4 @@
+package foo
+
+fun regularJs() {
+}
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/after/jvmMain/jvmMain.iml b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/after/jvmMain/jvmMain.iml
new file mode 100644
index 000000000000..539cd10de04b
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/after/jvmMain/jvmMain.iml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ commonMain
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/after/jvmMain/src/bar/target.jvm.kt b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/after/jvmMain/src/bar/target.jvm.kt
new file mode 100644
index 000000000000..2c2c08191dc9
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/after/jvmMain/src/bar/target.jvm.kt
@@ -0,0 +1,6 @@
+package bar
+
+actual fun foo(): Int = 0
+
+fun regularJvm() {
+}
\ No newline at end of file
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/after/jvmMain/src/foo/.keep b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/after/jvmMain/src/foo/.keep
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/before/commonMain/commonMain.iml b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/before/commonMain/commonMain.iml
new file mode 100644
index 000000000000..7cd42c203d42
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/before/commonMain/commonMain.iml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/before/commonMain/src/foo/.keep b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/before/commonMain/src/foo/.keep
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/before/commonMain/src/foo/base.kt b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/before/commonMain/src/foo/base.kt
new file mode 100644
index 000000000000..ca56072568a0
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/before/commonMain/src/foo/base.kt
@@ -0,0 +1,6 @@
+package foo
+
+expect fun foo(): Int
+
+fun regularCommon() {
+}
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/before/jsMain/jsMain.iml b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/before/jsMain/jsMain.iml
new file mode 100644
index 000000000000..65b5735a6e4a
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/before/jsMain/jsMain.iml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ commonMain
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/before/jsMain/src/bar/target.js.kt b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/before/jsMain/src/bar/target.js.kt
new file mode 100644
index 000000000000..913a5d85a089
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/before/jsMain/src/bar/target.js.kt
@@ -0,0 +1,4 @@
+package bar
+
+fun existingInTargetPackageJs() {
+}
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/before/jsMain/src/foo/.keep b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/before/jsMain/src/foo/.keep
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/before/jsMain/src/foo/base.js.kt b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/before/jsMain/src/foo/base.js.kt
new file mode 100644
index 000000000000..af95738a4315
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/before/jsMain/src/foo/base.js.kt
@@ -0,0 +1,6 @@
+package foo
+
+actual fun foo(): Int = 1
+
+fun regularJs() {
+}
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/before/jvmMain/jvmMain.iml b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/before/jvmMain/jvmMain.iml
new file mode 100644
index 000000000000..539cd10de04b
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/before/jvmMain/jvmMain.iml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ commonMain
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/before/jvmMain/src/foo/.keep b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/before/jvmMain/src/foo/.keep
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/before/jvmMain/src/foo/base.jvm.kt b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/before/jvmMain/src/foo/base.jvm.kt
new file mode 100644
index 000000000000..61dbefbd63a9
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/before/jvmMain/src/foo/base.jvm.kt
@@ -0,0 +1,6 @@
+package foo
+
+actual fun foo(): Int = 0
+
+fun regularJvm() {
+}
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/moveActualAndRegularFunctionConventionalSourceSetNames.test b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/moveActualAndRegularFunctionConventionalSourceSetNames.test
new file mode 100644
index 000000000000..b9c3aebcc49e
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualAndRegularFunctionConventionalSourceSetNames/moveActualAndRegularFunctionConventionalSourceSetNames.test
@@ -0,0 +1,8 @@
+{
+ "mainFile": "jvmMain/src/foo/base.jvm.kt",
+ "targetPackage": "bar",
+ "withRuntime": "true",
+ "modulesWithCommonRuntime": ["commonMain"],
+ "modulesWithRuntime": ["jvmMain"],
+ "modulesWithJsRuntime": ["jsMain"]
+}
\ No newline at end of file
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualClass/after/B/src/bar/Foo.kt b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualClass/after/B/src/bar/Foo.B.kt
similarity index 100%
rename from plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualClass/after/B/src/bar/Foo.kt
rename to plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualClass/after/B/src/bar/Foo.B.kt
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualClass/after/C/src/bar/Foo.kt b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualClass/after/C/src/bar/Foo.C.kt
similarity index 100%
rename from plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualClass/after/C/src/bar/Foo.kt
rename to plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualClass/after/C/src/bar/Foo.C.kt
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualFunction/after/B/src/bar/foo.kt b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualFunction/after/B/src/bar/foo.B.kt
similarity index 100%
rename from plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualFunction/after/B/src/bar/foo.kt
rename to plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualFunction/after/B/src/bar/foo.B.kt
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualFunction/after/C/src/bar/foo.kt b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualFunction/after/C/src/bar/foo.C.kt
similarity index 100%
rename from plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualFunction/after/C/src/bar/foo.kt
rename to plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveActualFunction/after/C/src/bar/foo.C.kt
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveExpectFunctionToExistingPackage/after/B/src/bar/foo.kt b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveExpectFunctionToExistingPackage/after/B/src/bar/foo.B.kt
similarity index 100%
rename from plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveExpectFunctionToExistingPackage/after/B/src/bar/foo.kt
rename to plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveExpectFunctionToExistingPackage/after/B/src/bar/foo.B.kt
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveExpectFunctionToExistingPackage/after/C/src/bar/foo.kt b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveExpectFunctionToExistingPackage/after/C/src/bar/foo.C.kt
similarity index 100%
rename from plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveExpectFunctionToExistingPackage/after/C/src/bar/foo.kt
rename to plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveExpectFunctionToExistingPackage/after/C/src/bar/foo.C.kt
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveExpectProperty/after/B/src/bar/test.kt b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveExpectProperty/after/B/src/bar/test.B.kt
similarity index 100%
rename from plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveExpectProperty/after/B/src/bar/test.kt
rename to plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveExpectProperty/after/B/src/bar/test.B.kt
diff --git a/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveExpectProperty/after/C/src/bar/test.kt b/plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveExpectProperty/after/C/src/bar/test.C.kt
similarity index 100%
rename from plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveExpectProperty/after/C/src/bar/test.kt
rename to plugins/kotlin/idea/tests/testData/refactoring/moveDescriptors/moveExpectProperty/after/C/src/bar/test.C.kt
diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/BUILD.bazel b/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/BUILD.bazel
index e0c720fdfc41..9e365634963d 100644
--- a/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/BUILD.bazel
+++ b/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/BUILD.bazel
@@ -58,6 +58,8 @@ jvm_library(
"//plugins/kotlin/base/facet",
"//platform/observable",
"//java/openapi:java",
+ "@lib//:kotlinc-analysis-api-platform-interface",
+ "//plugins/kotlin/base/external-build-system:kotlin-base-externalSystem",
]
)
@@ -115,6 +117,8 @@ jvm_library(
"//plugins/kotlin/base/facet",
"//platform/observable",
"//java/openapi:java",
+ "@lib//:kotlinc-analysis-api-platform-interface",
+ "//plugins/kotlin/base/external-build-system:kotlin-base-externalSystem",
]
)
### auto-generated section `build intellij.kotlin.refactorings.move` end
diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/intellij.kotlin.refactorings.move.iml b/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/intellij.kotlin.refactorings.move.iml
index 178a8d50826b..5e5966dbbf5a 100644
--- a/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/intellij.kotlin.refactorings.move.iml
+++ b/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/intellij.kotlin.refactorings.move.iml
@@ -68,5 +68,7 @@
+
+
\ No newline at end of file
diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/resources/intellij.kotlin.refactorings.move.xml b/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/resources/intellij.kotlin.refactorings.move.xml
index a55d7f5dda0d..284e4db328ae 100644
--- a/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/resources/intellij.kotlin.refactorings.move.xml
+++ b/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/resources/intellij.kotlin.refactorings.move.xml
@@ -1,6 +1,7 @@
+
(private val value: T) : ObservableProperty {
+ final override fun get(): T = value
+ final override fun afterChange(listener: (T) -> Unit) {}
+ final override fun afterChange(parentDisposable: Disposable?, listener: (T) -> Unit) {}
+}
+
+class ConstantBooleanObservableProperty(value: Boolean) : ConstantObservableProperty(value), ObservableBooleanProperty {
+ override fun afterSet(parentDisposable: Disposable?, listener: () -> Unit) {}
+ override fun afterReset(parentDisposable: Disposable?, listener: () -> Unit) {}
+}
diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/move/ui/K2MoveModel.kt b/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/move/ui/K2MoveModel.kt
index 83e7d42e3bb4..de06116d358e 100644
--- a/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/move/ui/K2MoveModel.kt
+++ b/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/move/ui/K2MoveModel.kt
@@ -4,7 +4,8 @@ package org.jetbrains.kotlin.idea.k2.refactoring.move.ui
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.observable.properties.AtomicBooleanProperty
import com.intellij.openapi.observable.properties.MutableBooleanProperty
-import com.intellij.openapi.observable.properties.ObservableBooleanProperty
+import com.intellij.openapi.observable.properties.ObservableProperty
+import com.intellij.openapi.observable.util.transform
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ProjectFileIndex
import com.intellij.openapi.util.NlsContexts
@@ -33,15 +34,13 @@ import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedDeclaration
-import org.jetbrains.kotlin.psi.psiUtil.hasActualModifier
-import org.jetbrains.kotlin.psi.psiUtil.isExpectDeclaration
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
/**
* @see K2MoveDescriptor
*/
-sealed class K2MoveModel(observableUiSettings: ObservableUiSettings) : K2MoveModelObservableSettings {
+sealed class K2MoveModel(private val observableUiSettings: ObservableUiSettings) : K2MoveModelObservableSettings {
init {
observableUiSettings.registerK2MoveModelSettings(this)
}
@@ -76,6 +75,10 @@ sealed class K2MoveModel(observableUiSettings: ObservableUiSettings) : K2MoveMod
}
open fun buildPanel(panel: Panel): Unit = with(panel) {
+ val mppDeclarationSelectedObservable = observableUiSettings.mppDeclarationsSelectedObservable.transform { isSelected ->
+ isSelected && inSourceRoot
+ }
+
row {
panel {
searchForText.createComboBox(this)
@@ -83,21 +86,25 @@ sealed class K2MoveModel(observableUiSettings: ObservableUiSettings) : K2MoveMod
}.align(AlignY.TOP + AlignX.LEFT)
panel {
searchInComments.createComboBox(this)
- mppDeclarations.createComboBox(this, inSourceRoot && this@K2MoveModel is Declarations)
+ mppDeclarations.createComboBox(
+ panel = this,
+ visible = mppDeclarationSelectedObservable,
+ enabled = mppDeclarationSelectedObservable,
+ )
}.align(AlignY.TOP + AlignX.RIGHT)
}
}
- override val mppDeclarationsObservable: ObservableBooleanProperty
+ override val mppDeclarationsSettingObservable: ObservableProperty
get() = mppDeclarations.observableProperty
- override val searchForTextObservable: ObservableBooleanProperty
+ override val searchForTextSettingObservable: ObservableProperty
get() = searchForText.observableProperty
- override val searchInCommentsObservable: ObservableBooleanProperty
+ override val searchInCommentsSettingObservable: ObservableProperty
get() = searchInComments.observableProperty
- override val searchReferencesObservable: ObservableBooleanProperty
+ override val searchReferencesSettingObservable: ObservableProperty
get() = searchReferences.observableProperty
enum class Setting(private val text: @NlsContexts.Checkbox String) {
@@ -143,19 +150,36 @@ sealed class K2MoveModel(observableUiSettings: ObservableUiSettings) : K2MoveMod
};
abstract var state: Boolean
- // lazy prevents service access from constructor
+ // lazy to avoid service access from constructor
internal val observableProperty: MutableBooleanProperty by lazy { AtomicBooleanProperty(state) }
- fun createComboBox(panel: Panel, enabled: Boolean = true) {
+ fun createComboBox(
+ panel: Panel,
+ enabled: Boolean = true,
+ visible: Boolean = true,
+ ) {
+ createComboBox(
+ panel = panel,
+ enabled = ConstantBooleanObservableProperty(enabled),
+ visible = ConstantBooleanObservableProperty(visible)
+ )
+ }
+
+ fun createComboBox(
+ panel: Panel,
+ enabled: ObservableProperty,
+ visible: ObservableProperty,
+ ) {
panel.row {
- val checkBox = checkBox(text).enabled(enabled)
- if (enabled) {
- checkBox.bindSelected(::state)
- // bind doesn't register state changes before dialog confirmation, but the UI should be updated on all state changes
- checkBox.onChanged { observableProperty.set(it.isSelected) }
- } else {
- checkBox.selected(false)
- }
+ val checkBox = checkBox(text).enabledIf(enabled).visibleIf(visible)
+ checkBox.onChanged { observableProperty.set(it.isSelected) }
+ // reset only is intentional
+ enabled.afterChange { isEnabled -> if (!isEnabled) checkBox.selected(false) }
+ checkBox.bindSelected(::state)
+ // value set in bindSelected doesn't trigger listeners
+ val initial = enabled.get() && state
+ checkBox.selected(initial)
+ observableProperty.set(initial)
}.layout(RowLayout.PARENT_GRID)
}
}
@@ -226,7 +250,7 @@ sealed class K2MoveModel(observableUiSettings: ObservableUiSettings) : K2MoveMod
&& name == fileName
}
if (!fileName.isValidKotlinFile()) return false
- val files = source.elements.map { it.containingFile }
+ val files = source.elements.map { it.containingFile }.toSet()
return files.size != 1 || !(files.single() as KtFile).isTargetFile() || target.isMoveToExplicitPackage()
}
@@ -258,7 +282,7 @@ sealed class K2MoveModel(observableUiSettings: ObservableUiSettings) : K2MoveMod
val searchForReferences = if (inSourceRoot) searchReferences.state else false
val searchForText = searchForText.state
val searchInComments = searchInComments.state
- if (mppDeclarations.state && declarations.any { it.isExpectDeclaration() || it.hasActualModifier() }) {
+ if (mppDeclarations.state && declarations.any { it.isExpectOrActual() }) {
val descriptors = declarations.flatMap { elem ->
ExpectActualUtils.withExpectedActuals(elem).filterIsInstance()
}.groupBy { elem ->
@@ -340,7 +364,7 @@ sealed class K2MoveModel(observableUiSettings: ObservableUiSettings) : K2MoveMod
|| declarationsFromFiles.isEmpty()
|| (targetContainer is PsiDirectory && targetContainer.getPackage() == null) -> {
// this move can contain foreign language files
- val source = K2MoveSourceModel.FileSource(elementsToMove.toFileElements().toSet())
+ val source = K2MoveSourceModel.FileSource(elementsToMove.toFileElements().toSet(), observableUiSettings)
val target = if (targetContainer is PsiDirectory) {
val pkg = targetContainer.getFqNameWithImplicitPrefixOrRoot()
K2MoveTargetModel.SourceDirectory(
@@ -360,7 +384,7 @@ sealed class K2MoveModel(observableUiSettings: ObservableUiSettings) : K2MoveMod
}
targetContainer is KtFile || targetContainer.isSingleClassContainer() || isSingleFileMove(elementsToMove) -> {
- val source = K2MoveSourceModel.ElementSource(declarationsFromFiles.toSet())
+ val source = K2MoveSourceModel.ElementSource(declarationsFromFiles.toSet(), observableUiSettings)
val targetFile = targetContainer?.containingFile
val target = if (targetFile is KtFile) {
K2MoveTargetModel.File(targetFile, observableUiSettings)
diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/move/ui/K2MoveSourceModel.kt b/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/move/ui/K2MoveSourceModel.kt
index 8a8aa9e93710..9b89b1ca79a0 100644
--- a/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/move/ui/K2MoveSourceModel.kt
+++ b/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/move/ui/K2MoveSourceModel.kt
@@ -2,6 +2,9 @@
package org.jetbrains.kotlin.idea.k2.refactoring.move.ui
import com.intellij.openapi.actionSystem.ex.ActionUtil
+import com.intellij.openapi.observable.properties.AtomicBooleanProperty
+import com.intellij.openapi.observable.properties.MutableBooleanProperty
+import com.intellij.openapi.observable.properties.ObservableBooleanProperty
import com.intellij.platform.backend.presentation.TargetPresentation
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFileSystemItem
@@ -13,6 +16,7 @@ import com.intellij.ui.dsl.builder.BottomGap
import com.intellij.ui.dsl.builder.Panel
import com.intellij.ui.dsl.builder.TopGap
import com.intellij.ui.list.createTargetPresentationRenderer
+import org.jetbrains.annotations.ApiStatus
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.k2.refactoring.move.descriptor.K2MoveSourceDescriptor
import org.jetbrains.kotlin.idea.refactoring.memberInfo.AbstractKotlinMemberInfoModel
@@ -23,15 +27,25 @@ import org.jetbrains.kotlin.psi.KtDeclarationContainer
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import javax.swing.JComponent
-sealed interface K2MoveSourceModel {
- val elements: Set
+sealed class K2MoveSourceModel(
+ observableUiSettings: ObservableUiSettings
+): K2SourceModelObservableSettings {
+ abstract val elements: Set
- fun toDescriptor(): K2MoveSourceDescriptor?
+ abstract fun toDescriptor(): K2MoveSourceDescriptor?
- fun buildPanel(panel: Panel, onError: (String?, JComponent) -> Unit, revalidateButtons: () -> Unit)
+ abstract fun buildPanel(panel: Panel, onError: (String?, JComponent) -> Unit, revalidateButtons: () -> Unit)
- class FileSource(fsItems: Set) : K2MoveSourceModel {
+ init {
+ observableUiSettings.registerK2SourceModelSettings(this)
+ }
+
+ class FileSource(
+ fsItems: Set,
+ observableUiSettings: ObservableUiSettings,
+ ) : K2MoveSourceModel(observableUiSettings) {
override var elements: Set = fsItems
+ override val mppDeclarationsSelectedObservable: ObservableBooleanProperty = ConstantBooleanObservableProperty(false)
override fun toDescriptor(): K2MoveSourceDescriptor.FileSource = K2MoveSourceDescriptor.FileSource(elements)
@@ -47,9 +61,8 @@ sealed interface K2MoveSourceModel {
}
panel.group(RefactoringBundle.message("move.files.group")) {
- lateinit var list: JBList
row {
- list = cell(JBList(CollectionListModel(presentableFiles)).apply {
+ cell(JBList(CollectionListModel(presentableFiles)).apply {
cellRenderer = createTargetPresentationRenderer { it }
}).align(Align.FILL).component
}.resizableRow()
@@ -57,9 +70,14 @@ sealed interface K2MoveSourceModel {
}
}
- class ElementSource(declarations: Set) : K2MoveSourceModel {
+ class ElementSource(
+ declarations: Set,
+ observableUiSettings: ObservableUiSettings,
+ ) : K2MoveSourceModel(observableUiSettings) {
override var elements: Set = declarations
private set
+ override val mppDeclarationsSelectedObservable: MutableBooleanProperty =
+ AtomicBooleanProperty(hasExpectOrActualElements())
private lateinit var memberSelectionPanel: KotlinMemberSelectionPanel
@@ -83,6 +101,7 @@ sealed interface K2MoveSourceModel {
val table = memberSelectionPanel.table
table.addMemberInfoChangeListener {
elements = table.selectedMemberInfos.map { it.member }.toSet()
+ mppDeclarationsSelectedObservable.set(hasExpectOrActualElements())
if (elements.isEmpty()) {
onError(KotlinBundle.message("text.no.elements.to.move.are.selected"), memberSelectionPanel.table)
} else {
@@ -116,6 +135,9 @@ sealed interface K2MoveSourceModel {
isChecked = elementsToMove.contains(declaration)
}
}
+
+ private fun hasExpectOrActualElements(): Boolean =
+ elements.any { it.isExpectOrActual() }
}
}
diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/move/ui/K2MoveTargetModel.kt b/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/move/ui/K2MoveTargetModel.kt
index 32931474806e..3472fdf95a04 100644
--- a/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/move/ui/K2MoveTargetModel.kt
+++ b/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/move/ui/K2MoveTargetModel.kt
@@ -6,9 +6,11 @@ import com.intellij.ide.util.DirectoryChooser
import com.intellij.ide.util.TreeJavaClassChooserDialog
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.application.ReadAction
-import com.intellij.openapi.observable.properties.AtomicBooleanProperty
-import com.intellij.openapi.observable.properties.MutableBooleanProperty
-import com.intellij.openapi.observable.properties.PropertyGraph
+import com.intellij.openapi.observable.properties.*
+import com.intellij.openapi.observable.util.and
+import com.intellij.openapi.observable.util.equalsTo
+import com.intellij.openapi.observable.util.operation
+import com.intellij.openapi.observable.util.transform
import com.intellij.openapi.roots.ProjectFileIndex
import com.intellij.openapi.ui.TextFieldWithBrowseButton
import com.intellij.openapi.util.registry.Registry
@@ -33,37 +35,42 @@ import org.jetbrains.kotlin.idea.core.util.toPsiDirectory
import org.jetbrains.kotlin.idea.k2.refactoring.move.descriptor.K2MoveTargetDescriptor
import org.jetbrains.kotlin.idea.refactoring.ui.KotlinDestinationFolderComboBox
import org.jetbrains.kotlin.idea.refactoring.ui.KotlinFileChooserDialog
+import org.jetbrains.kotlin.idea.util.sourceRoot
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtFile
import javax.swing.JComponent
import javax.swing.event.DocumentEvent
-sealed interface K2MoveTargetModel {
- val directory: PsiDirectory
+sealed class K2MoveTargetModel(
+ protected val observableUiSettings: ObservableUiSettings
+) : K2TargetModelObservableSettings {
+ init {
+ observableUiSettings.registerK2TargetModelSettings(this)
+ }
- val pkgName: FqName
+ abstract val directory: PsiDirectory
- val explicitPkgMoveFqName: FqName?
+ abstract val pkgName: FqName
- val observableUiSettings: ObservableUiSettings
+ abstract val explicitPkgMoveFqName: FqName?
/**
* Creates a [K2MoveTargetDescriptor] from this model.
* @param kmpSourceRoot source KMP directory that corresponds to the base [directory] in a different source set.
* `null` for non expect-actual moves.
*/
- fun toDescriptor(kmpSourceRoot: PsiDirectory? = null): K2MoveTargetDescriptor
+ abstract fun toDescriptor(kmpSourceRoot: PsiDirectory? = null): K2MoveTargetDescriptor
- fun buildPanel(panel: Panel, onError: (String?, JComponent) -> Unit, revalidateButtons: () -> Unit)
+ abstract fun buildPanel(panel: Panel, onError: (String?, JComponent) -> Unit, revalidateButtons: () -> Unit)
@ApiStatus.Internal
abstract class SourceDirectoryChooser(
override var pkgName: FqName,
override var directory: PsiDirectory,
override val explicitPkgMoveFqName: FqName?,
- override val observableUiSettings: ObservableUiSettings,
- ) : K2MoveTargetModel {
+ observableUiSettings: ObservableUiSettings,
+ ) : K2MoveTargetModel(observableUiSettings) {
private val initialDirectory = directory
protected lateinit var pkgChooser: PackageNameReferenceEditorCombo
@@ -163,20 +170,24 @@ sealed interface K2MoveTargetModel {
override fun buildPanel(panel: Panel, onError: (String?, JComponent) -> Unit, revalidateButtons: () -> Unit) {
panel.installPkgChooser(onError, revalidateButtons)
}
+
+ override val sourceSetSuffix: ObservableProperty
+ get() = ConstantObservableProperty(null)
}
@ApiStatus.Internal
abstract class FileChooser(
- fileName: String,
+ @set:ApiStatus.Internal
+ var fileName: String,
pkg: FqName,
directory: PsiDirectory,
explicitPkgMoveFqName: FqName?,
observableUiSettings: ObservableUiSettings,
) : SourceDirectoryChooser(pkg, directory, explicitPkgMoveFqName, observableUiSettings) {
- var fileName: String = fileName
- protected set
private var selectedFile: KtFile? = null
+ private val mppSuffixObservableProperty: AtomicProperty =
+ AtomicProperty(findFileSuffix(fileName))
override fun updateDirectory(onError: (String?, JComponent) -> Unit, revalidateButtons: () -> Unit) {
super.updateDirectory(onError, revalidateButtons)
@@ -220,10 +231,21 @@ sealed interface K2MoveTargetModel {
} else {
onError(null, fileChooser)
}
+ mppSuffixObservableProperty.set(findFileSuffix(fileName))
revalidateButtons()
}
})
}
+
+ override val sourceSetSuffix: ObservableProperty
+ get() = mppSuffixObservableProperty
+
+ private fun findFileSuffix(fileName: String): String? {
+ val sourceRootVirtualFile = directory.sourceRoot ?: return null
+ val sourceRootPsiDir = sourceRootVirtualFile.toPsiDirectory(directory.project) ?: return null
+ val suffix = findSourceSetNameStem(sourceRootPsiDir)
+ return if (fileName.endsWith(".$suffix.kt")) suffix else null
+ }
}
class File(
@@ -274,7 +296,8 @@ sealed interface K2MoveTargetModel {
return if (destinationTargetType == MoveTargetType.CLASS && selectedClass != null) {
K2MoveTargetDescriptor.ClassOrObject(selectedClass)
} else {
- K2MoveTargetDescriptor.File(fileName, pkgName, kmpSourceRoot ?: directory)
+ val fileNameWithKmpSuffixHandled = findSuffixedFileName(fileName, kmpSourceRoot)
+ K2MoveTargetDescriptor.File(fileNameWithKmpSuffixHandled, pkgName, kmpSourceRoot ?: directory)
}
}
@@ -322,43 +345,92 @@ sealed interface K2MoveTargetModel {
})
}
+ private fun Panel.installTargetFileSelector(
+ onError: (String?, JComponent) -> Unit,
+ revalidateButtons: () -> Unit,
+ installTitleElement: Row.() -> Unit,
+ ) {
+ val iconInfoObservableProperty = observableUiSettings.mppDeclarationsSettingObservable
+ .and(observableUiSettings.sourceSetSuffix.transform { it.isNullOrBlank() })
+ val iconWarningObservableProperty = observableUiSettings.mppDeclarationsSettingObservable
+ .and(observableUiSettings.sourceSetSuffix.transform { !it.isNullOrBlank() })
+
+ row {
+ panel {
+ row {
+ installTitleElement()
+ icon(AllIcons.General.ContextHelp).align(AlignX.RIGHT).gap(RightGap.SMALL).applyToComponent {
+ toolTipText = RefactoringBundle.message("tooltip.text.move.kmp.target.file")
+ }.visibleIf(observableUiSettings.mppDeclarationsSettingObservable)
+ }
+ }
+ installFileChooser(onError, revalidateButtons)
+ }.layout(RowLayout.LABEL_ALIGNED)
+ row("") {
+ icon(AllIcons.General.Warning).align(AlignX.LEFT).gap(RightGap.SMALL)
+ .visibleIf(iconWarningObservableProperty)
+ icon(AllIcons.General.Information).align(AlignX.LEFT).gap(RightGap.SMALL)
+ .visibleIf(iconInfoObservableProperty)
+ val warningLabel = label("").bindText(
+ operation(iconInfoObservableProperty, iconWarningObservableProperty) { info, warning ->
+ when {
+ info -> RefactoringBundle.message("move.added.kmp.suffixes.info")
+ warning -> RefactoringBundle.message(
+ "move.added.kmp.suffixes.warning.0",
+ observableUiSettings.sourceSetSuffix.get().orEmpty(),
+ )
+ else -> ""
+ }
+ }
+ ).visibleIf(observableUiSettings.mppDeclarationsSettingObservable)
+ // placeholder to avoid resizing on changes in the settings
+ label("").visibleIf(observableUiSettings.mppDeclarationsSettingObservable.equalsTo(false))
+ .applyToComponent { preferredSize = warningLabel.component.preferredSize }
+ }
+ }
+
+ private fun Panel.installTargetSelectionRadioGroup(
+ onError: (String?, JComponent) -> Unit,
+ revalidateButtons: () -> Unit,
+ ) {
+ buttonsGroup(indent = false) {
+ installTargetFileSelector(onError, revalidateButtons) {
+ radioButton(KotlinBundle.message("refactoring.file.destination"), MoveTargetType.FILE)
+ .onChanged {
+ destinationTargetType = MoveTargetType.FILE
+ fileChooser.isEnabled = true
+ pkgChooser.isEnabled = true
+ destinationChooser.isEnabled = true
+ classChooser.isEnabled = false
+ revalidateButtons()
+ }
+ }
+ row {
+ radioButton(KotlinBundle.message("refactoring.class.destination"), MoveTargetType.CLASS)
+ .onChanged {
+ destinationTargetType = MoveTargetType.CLASS
+ fileChooser.isEnabled = false
+ pkgChooser.isEnabled = false
+ destinationChooser.isEnabled = false
+ classChooser.isEnabled = true
+ revalidateButtons()
+ }
+ installClassTargetChooser(onError, revalidateButtons)
+ }.layout(RowLayout.LABEL_ALIGNED)
+ }.bind(::destinationTargetType.toMutableProperty())
+ }
+
override fun buildPanel(
panel: Panel,
onError: (String?, JComponent) -> Unit,
revalidateButtons: () -> Unit
) {
panel.installPkgChooser(onError, revalidateButtons)
-
if (Registry.`is`("kotlin.move.show.move.to.class")) {
- panel.buttonsGroup(indent = true) {
- panel.row {
- radioButton(KotlinBundle.message("refactoring.file.destination"), MoveTargetType.FILE)
- .onChanged {
- destinationTargetType = MoveTargetType.FILE
- fileChooser.isEnabled = true
- pkgChooser.isEnabled = true
- destinationChooser.isEnabled = true
- classChooser.isEnabled = false
- revalidateButtons()
- }
- installFileChooser(onError, revalidateButtons)
- }
- panel.row {
- radioButton(KotlinBundle.message("refactoring.class.destination"), MoveTargetType.CLASS)
- .onChanged {
- destinationTargetType = MoveTargetType.CLASS
- fileChooser.isEnabled = false
- pkgChooser.isEnabled = false
- destinationChooser.isEnabled = false
- classChooser.isEnabled = true
- revalidateButtons()
- }
- installClassTargetChooser(onError, revalidateButtons)
- }
- }.bind(::destinationTargetType.toMutableProperty())
+ panel.installTargetSelectionRadioGroup(onError, revalidateButtons)
} else {
- panel.row {
- installFileChooser(onError, revalidateButtons)
+ panel.installTargetFileSelector(onError, revalidateButtons) {
+ label(KotlinBundle.message("label.text.file"))
}
}
}
diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/move/ui/ObservableUiSettings.kt b/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/move/ui/ObservableUiSettings.kt
index 005c5004377f..7eceed61f446 100644
--- a/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/move/ui/ObservableUiSettings.kt
+++ b/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/move/ui/ObservableUiSettings.kt
@@ -2,32 +2,58 @@
package org.jetbrains.kotlin.idea.k2.refactoring.move.ui
-import com.intellij.openapi.observable.properties.ObservableBooleanProperty
+import com.intellij.openapi.observable.properties.ObservableProperty
-interface ObservableUiSettings : K2MoveModelObservableSettings {
+interface ObservableUiSettings : K2MoveModelObservableSettings, K2SourceModelObservableSettings, K2TargetModelObservableSettings {
fun registerK2MoveModelSettings(settings: K2MoveModelObservableSettings)
+ fun registerK2SourceModelSettings(settings: K2SourceModelObservableSettings)
+ fun registerK2TargetModelSettings(settings: K2TargetModelObservableSettings)
}
interface K2MoveModelObservableSettings {
- val searchReferencesObservable: ObservableBooleanProperty
- val searchForTextObservable: ObservableBooleanProperty
- val searchInCommentsObservable: ObservableBooleanProperty
- val mppDeclarationsObservable: ObservableBooleanProperty
+ val searchReferencesSettingObservable: ObservableProperty
+ val searchForTextSettingObservable: ObservableProperty
+ val searchInCommentsSettingObservable: ObservableProperty
+ val mppDeclarationsSettingObservable: ObservableProperty
+}
+
+interface K2SourceModelObservableSettings {
+ val mppDeclarationsSelectedObservable: ObservableProperty
+}
+
+interface K2TargetModelObservableSettings {
+ val sourceSetSuffix: ObservableProperty
}
internal class ObservableUiSettingsImpl : ObservableUiSettings {
private lateinit var k2MoveModelSettings: K2MoveModelObservableSettings
+ private lateinit var k2TargetModelSettings: K2TargetModelObservableSettings
+ private lateinit var k2SourceModelSettings: K2SourceModelObservableSettings
- override val searchReferencesObservable: ObservableBooleanProperty
- get() = k2MoveModelSettings.searchReferencesObservable
- override val searchForTextObservable: ObservableBooleanProperty
- get() = k2MoveModelSettings.searchForTextObservable
- override val searchInCommentsObservable: ObservableBooleanProperty
- get() = k2MoveModelSettings.searchInCommentsObservable
- override val mppDeclarationsObservable: ObservableBooleanProperty
- get() = k2MoveModelSettings.mppDeclarationsObservable
+ override val searchReferencesSettingObservable: ObservableProperty
+ get() = k2MoveModelSettings.searchReferencesSettingObservable
+ override val searchForTextSettingObservable: ObservableProperty
+ get() = k2MoveModelSettings.searchForTextSettingObservable
+ override val searchInCommentsSettingObservable: ObservableProperty
+ get() = k2MoveModelSettings.searchInCommentsSettingObservable
+ override val mppDeclarationsSettingObservable: ObservableProperty
+ get() = k2MoveModelSettings.mppDeclarationsSettingObservable
+
+ override val mppDeclarationsSelectedObservable: ObservableProperty
+ get() = k2SourceModelSettings.mppDeclarationsSelectedObservable
+
+ override val sourceSetSuffix: ObservableProperty
+ get() = k2TargetModelSettings.sourceSetSuffix
override fun registerK2MoveModelSettings(settings: K2MoveModelObservableSettings) {
k2MoveModelSettings = settings
}
+
+ override fun registerK2SourceModelSettings(settings: K2SourceModelObservableSettings) {
+ k2SourceModelSettings = settings
+ }
+
+ override fun registerK2TargetModelSettings(settings: K2TargetModelObservableSettings) {
+ k2TargetModelSettings = settings
+ }
}
diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/move/ui/moveUiUtil.kt b/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/move/ui/moveUiUtil.kt
index a0d2b964c95d..5bd0e4055016 100644
--- a/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/move/ui/moveUiUtil.kt
+++ b/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/move/ui/moveUiUtil.kt
@@ -7,13 +7,18 @@ import com.intellij.psi.PsiDirectory
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFileSystemItem
import com.intellij.psi.util.parentOfType
+import org.jetbrains.kotlin.analysis.api.projectStructure.KaModuleProvider
import org.jetbrains.kotlin.idea.KotlinLanguage
+import org.jetbrains.kotlin.idea.base.externalSystem.KotlinBuildSystemFacade
+import org.jetbrains.kotlin.idea.base.util.module
import org.jetbrains.kotlin.idea.core.getImplicitPackagePrefix
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedDeclaration
+import org.jetbrains.kotlin.psi.psiUtil.hasActualModifier
+import org.jetbrains.kotlin.psi.psiUtil.isExpectDeclaration
internal fun String.isValidKotlinFile(): Boolean {
return endsWith(KotlinLanguage.INSTANCE.associatedFileType?.defaultExtension ?: return false) || endsWith(".kts")
@@ -65,3 +70,29 @@ internal fun findExplicitPkgMoveFqName(elementsToMove: List): FqName
internal fun K2MoveTargetModel.isMoveToExplicitPackage(): Boolean =
pkgName == explicitPkgMoveFqName
+
+internal fun KtNamedDeclaration.isExpectOrActual(): Boolean =
+ isExpectDeclaration() || hasActualModifier()
+
+internal fun findSourceSetNameStem(kmpSourceRoot: PsiDirectory): String? {
+ val project = kmpSourceRoot.project
+ val kaModule = KaModuleProvider.getModule(project, kmpSourceRoot, useSiteModule = null)
+ val dependsOnDependencies = kaModule.directDependsOnDependencies
+ if (dependsOnDependencies.isEmpty()) return null
+ val workspaceModule = kmpSourceRoot.module ?: return null
+ val sourceSet = KotlinBuildSystemFacade.getInstance().findSourceSet(workspaceModule)
+ val fullSourceSetName = sourceSet?.name ?: workspaceModule.name.split(".").last()
+ val sourceSetNameStem = kmpSourceSetDefaultSuffixes.firstOrNull { fullSourceSetName.endsWith(it) }
+ ?.let { sourceSetNameSuffix -> fullSourceSetName.removeSuffix(sourceSetNameSuffix) }
+ ?: fullSourceSetName
+ return sourceSetNameStem
+}
+
+internal fun findSuffixedFileName(baseFileName: String, kmpSourceRoot: PsiDirectory?): String {
+ if (kmpSourceRoot == null) return baseFileName
+ val sourceSetSuffix = findSourceSetNameStem(kmpSourceRoot) ?: return baseFileName
+ val baseWithoutKt = baseFileName.substringBeforeLast(".kt")
+ return "$baseWithoutKt.$sourceSetSuffix.kt"
+}
+
+private val kmpSourceSetDefaultSuffixes = listOf("Main", "Test")
diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/test/org/jetbrains/kotlin/idea/k2/refactoring/move/AbstractK2CheckDescriptorMultiModuleMoveTest.kt b/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/test/org/jetbrains/kotlin/idea/k2/refactoring/move/AbstractK2CheckDescriptorMultiModuleMoveTest.kt
index b2edb6369908..d4e09f1ddd00 100644
--- a/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/test/org/jetbrains/kotlin/idea/k2/refactoring/move/AbstractK2CheckDescriptorMultiModuleMoveTest.kt
+++ b/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/test/org/jetbrains/kotlin/idea/k2/refactoring/move/AbstractK2CheckDescriptorMultiModuleMoveTest.kt
@@ -41,8 +41,8 @@ abstract class AbstractK2CheckDescriptorMultiModuleMoveTest : KotlinMultiFileTes
config: JsonObject
) {
allowAnalysisOnEdt {
- val elementAtCaret = elementsAtCaret.single()
- val editor = elementAtCaret.findExistingEditor()
+ val firstElementAtCaret = elementsAtCaret.first()
+ val editor = firstElementAtCaret.findExistingEditor()
val moveModel = K2MoveModel.create(elementsAtCaret.toTypedArray(), null, editor)
?: error("Failed to create move model")
configureMoveModel(moveModel)
diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/test/org/jetbrains/kotlin/idea/k2/refactoring/move/K2CheckDescriptorMultiModuleMoveTest.kt b/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/test/org/jetbrains/kotlin/idea/k2/refactoring/move/K2CheckDescriptorMultiModuleMoveTest.kt
index 19de7ae34952..b0216262e7a9 100644
--- a/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/test/org/jetbrains/kotlin/idea/k2/refactoring/move/K2CheckDescriptorMultiModuleMoveTest.kt
+++ b/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/test/org/jetbrains/kotlin/idea/k2/refactoring/move/K2CheckDescriptorMultiModuleMoveTest.kt
@@ -54,6 +54,20 @@ class K2CheckDescriptorMultiModuleMoveTest : AbstractK2CheckDescriptorMultiModul
)
}
+ @TestMetadata("moveActualAndRegularFunctionConventionalSourceSetNames")
+ @Throws(Exception::class)
+ fun testMoveActualAndRegularFunctionConventionalSourceSetNames() {
+ doTest(
+ "moveActualAndRegularFunctionConventionalSourceSetNames/moveActualAndRegularFunctionConventionalSourceSetNames.test",
+ configureMoveModel = { moveModel ->
+ setAllMoveSettingsOn(moveModel)
+ setNewTargetPackageInSameRoot(moveModel, FqName("bar"))
+ setTargetFile(moveModel, "target.kt")
+ },
+ checkMoveDescriptor = { moveOperationDescriptor -> checkNoMoveOutsideSourceRoot(moveOperationDescriptor) },
+ )
+ }
+
@TestMetadata("moveActualClass")
@Throws(Exception::class)
fun testMoveActualClass() {
@@ -112,17 +126,28 @@ class K2CheckDescriptorMultiModuleMoveTest : AbstractK2CheckDescriptorMultiModul
targetModel.directory = packageSubdirectory
}
+ /**
+ * Imitates target file change through the file chooser.
+ */
+ private fun setTargetFile(moveModel: K2MoveModel, fileName: String) {
+ val moveTarget = moveModel.target
+ if (moveTarget !is K2MoveTargetModel.FileChooser)
+ throw AssertionError("Unexpected move target model: ${moveTarget::class.simpleName}")
+ moveTarget.fileName = fileName
+ }
+
private fun checkNoMoveOutsideSourceRoot(moveOperationDescriptor: K2MoveOperationDescriptor<*>) {
moveOperationDescriptor.moveDescriptors.forEach { descriptor ->
- val element = descriptor.source.elements.singleOrNull() ?: error("Single source element to move is expected")
- val elementSourceDir = element.containingFile.sourceRoot ?: error("Can't find source root for element")
- val targetRoot = descriptor.target.baseDirectory.sourceRoot ?: error("Can't find source root for target")
- assert(elementSourceDir == targetRoot) {
- """Element was unexpectedly moved outside of its source root:
+ descriptor.source.elements.forEach { element ->
+ val elementSourceDir = element.containingFile.sourceRoot ?: error("Can't find source root for element")
+ val targetRoot = descriptor.target.baseDirectory.sourceRoot ?: error("Can't find source root for target")
+ assert(elementSourceDir == targetRoot) {
+ """Element was unexpectedly moved outside of its source root:
|${element.text}
|Element root: ${elementSourceDir.path}
|Target root: ${targetRoot.path}
|""".trimMargin()
+ }
}
}
}