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 d1736c38d189..83e7d42e3bb4 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 @@ -2,6 +2,9 @@ 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.project.Project import com.intellij.openapi.roots.ProjectFileIndex import com.intellij.openapi.util.NlsContexts @@ -38,7 +41,11 @@ import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull /** * @see K2MoveDescriptor */ -sealed class K2MoveModel { +sealed class K2MoveModel(observableUiSettings: ObservableUiSettings) : K2MoveModelObservableSettings { + init { + observableUiSettings.registerK2MoveModelSettings(this) + } + abstract val project: Project abstract val source: K2MoveSourceModel<*> @@ -81,6 +88,18 @@ sealed class K2MoveModel { } } + override val mppDeclarationsObservable: ObservableBooleanProperty + get() = mppDeclarations.observableProperty + + override val searchForTextObservable: ObservableBooleanProperty + get() = searchForText.observableProperty + + override val searchInCommentsObservable: ObservableBooleanProperty + get() = searchInComments.observableProperty + + override val searchReferencesObservable: ObservableBooleanProperty + get() = searchReferences.observableProperty + enum class Setting(private val text: @NlsContexts.Checkbox String) { SEARCH_FOR_TEXT(KotlinBundle.message("search.for.text.occurrences")) { override var state: Boolean @@ -124,12 +143,16 @@ sealed class K2MoveModel { }; abstract var state: Boolean + // lazy prevents service access from constructor + internal val observableProperty: MutableBooleanProperty by lazy { AtomicBooleanProperty(state) } fun createComboBox(panel: Panel, enabled: Boolean = true) { 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) } @@ -145,8 +168,9 @@ sealed class K2MoveModel { override val source: K2MoveSourceModel.FileSource, override val target: K2MoveTargetModel.SourceDirectory, override val inSourceRoot: Boolean, - override val moveCallBack: MoveCallback? = null - ) : K2MoveModel() { + observableUiSettings: ObservableUiSettings, + override val moveCallBack: MoveCallback? = null, + ) : K2MoveModel(observableUiSettings) { private fun PsiFile.isAlreadyInTarget(): Boolean { return parent == target.directory && when (this) { is PsiJavaFile -> packageName == target.pkgName.asString() @@ -192,8 +216,9 @@ sealed class K2MoveModel { override val source: K2MoveSourceModel.ElementSource, override val target: K2MoveTargetModel, override val inSourceRoot: Boolean, + observableUiSettings: ObservableUiSettings, override val moveCallBack: MoveCallback? = null - ) : K2MoveModel() { + ) : K2MoveModel(observableUiSettings) { private fun isValidFileRefactoring(fileName: String): Boolean { fun KtFile.isTargetFile(): Boolean { return containingDirectory == target.directory @@ -307,6 +332,7 @@ sealed class K2MoveModel { } val inSourceRoot = isInSourceRoot(project, elementsToMove, targetContainer) val explicitPkgMoveFqName = findExplicitPkgMoveFqName(elementsToMove) + val observableUiSettings = ObservableUiSettingsImpl() return when { (elementsToMove.all { it is KtFile } && targetContainer is PsiDirectory) @@ -321,22 +347,23 @@ sealed class K2MoveModel { pkgName = pkg, directory = targetContainer, explicitPkgMoveFqName = explicitPkgMoveFqName, + observableUiSettings = observableUiSettings, ) } else { // no default target is provided, happens when invoking refactoring via keyboard instead of drag-and-drop val file = elementsToMove.firstOrNull { it.containingFile != null }?.containingFile ?: error("No default target found") val directory = file.containingDirectory ?: error("No default target found") val pkgName = elementsToMove.firstIsInstanceOrNull()?.containingKtFile?.packageFqName ?: FqName.ROOT - K2MoveTargetModel.SourceDirectory(pkgName, directory, explicitPkgMoveFqName = null) + K2MoveTargetModel.SourceDirectory(pkgName, directory, explicitPkgMoveFqName = null, observableUiSettings) } - Files(project, source, target, inSourceRoot, moveCallBack) + Files(project, source, target, inSourceRoot, observableUiSettings, moveCallBack) } targetContainer is KtFile || targetContainer.isSingleClassContainer() || isSingleFileMove(elementsToMove) -> { val source = K2MoveSourceModel.ElementSource(declarationsFromFiles.toSet()) val targetFile = targetContainer?.containingFile val target = if (targetFile is KtFile) { - K2MoveTargetModel.File(targetFile) + K2MoveTargetModel.File(targetFile, observableUiSettings) } else if (targetContainer is PsiDirectory) { val pkg = targetContainer.getFqNameWithImplicitPrefixOrRoot() K2MoveTargetModel.File( @@ -344,6 +371,7 @@ sealed class K2MoveModel { pkg = pkg, directory = targetContainer, explicitPkgMoveFqName = explicitPkgMoveFqName, + observableUiSettings = observableUiSettings, ) } else { // no default target is provided, happens when invoking refactoring via keyboard instead of drag-and-drop val firstElem = elementsToMove.firstOrNull() as KtElement @@ -352,7 +380,8 @@ sealed class K2MoveModel { K2MoveTargetModel.Declarations( defaultDirectory = psiDirectory, defaultPkgName = containingFile.packageFqName, - defaultFileName = findSourceFileNameByMovedElements(elementsToMove) + defaultFileName = findSourceFileNameByMovedElements(elementsToMove), + observableUiSettings = observableUiSettings, ) } @@ -361,6 +390,7 @@ sealed class K2MoveModel { source = source, target = target, inSourceRoot = inSourceRoot, + observableUiSettings = observableUiSettings, moveCallBack = moveCallBack ) } 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 1fd40b8dd39c..32931474806e 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 @@ -46,6 +46,8 @@ sealed interface K2MoveTargetModel { val explicitPkgMoveFqName: FqName? + val observableUiSettings: ObservableUiSettings + /** * Creates a [K2MoveTargetDescriptor] from this model. * @param kmpSourceRoot source KMP directory that corresponds to the base [directory] in a different source set. @@ -60,6 +62,7 @@ sealed interface K2MoveTargetModel { override var pkgName: FqName, override var directory: PsiDirectory, override val explicitPkgMoveFqName: FqName?, + override val observableUiSettings: ObservableUiSettings, ) : K2MoveTargetModel { private val initialDirectory = directory @@ -147,7 +150,8 @@ sealed interface K2MoveTargetModel { pkgName: FqName, directory: PsiDirectory, explicitPkgMoveFqName: FqName?, - ) : SourceDirectoryChooser(pkgName, directory, explicitPkgMoveFqName) { + observableUiSettings: ObservableUiSettings, + ) : SourceDirectoryChooser(pkgName, directory, explicitPkgMoveFqName, observableUiSettings) { override fun toDescriptor(kmpSourceRoot: PsiDirectory?): K2MoveTargetDescriptor.Directory { return K2MoveTargetDescriptor.Directory( pkgName = pkgName, @@ -167,7 +171,8 @@ sealed interface K2MoveTargetModel { pkg: FqName, directory: PsiDirectory, explicitPkgMoveFqName: FqName?, - ) : SourceDirectoryChooser(pkg, directory, explicitPkgMoveFqName) { + observableUiSettings: ObservableUiSettings, + ) : SourceDirectoryChooser(pkg, directory, explicitPkgMoveFqName, observableUiSettings) { var fileName: String = fileName protected set @@ -221,8 +226,13 @@ sealed interface K2MoveTargetModel { } } - class File(fileName: String, pkg: FqName, directory: PsiDirectory, explicitPkgMoveFqName: FqName?) : - FileChooser(fileName, pkg, directory, explicitPkgMoveFqName) { + class File( + fileName: String, + pkg: FqName, + directory: PsiDirectory, + explicitPkgMoveFqName: FqName?, + observableUiSettings: ObservableUiSettings, + ) : FileChooser(fileName, pkg, directory, explicitPkgMoveFqName, observableUiSettings) { override fun toDescriptor(kmpSourceRoot: PsiDirectory?): K2MoveTargetDescriptor.File = K2MoveTargetDescriptor.File( fileName = fileName, @@ -242,8 +252,9 @@ sealed interface K2MoveTargetModel { class Declarations( defaultDirectory: PsiDirectory, defaultPkgName: FqName, - defaultFileName: String - ) : FileChooser(defaultFileName, defaultPkgName, defaultDirectory, explicitPkgMoveFqName = null) { + defaultFileName: String, + observableUiSettings: ObservableUiSettings, + ) : FileChooser(defaultFileName, defaultPkgName, defaultDirectory, explicitPkgMoveFqName = null, observableUiSettings) { private val propertyGraph = PropertyGraph() private val destinationClassProperty = propertyGraph.property(null) @@ -354,9 +365,15 @@ sealed interface K2MoveTargetModel { } companion object { - fun File(file: KtFile): File { + fun File(file: KtFile, observableUiSettings: ObservableUiSettings): File { val directory = file.containingDirectory ?: error("No containing directory was found") - return File(file.name, file.packageFqName, directory, null) + return File( + fileName = file.name, + pkg = file.packageFqName, + directory = directory, + explicitPkgMoveFqName = null, + observableUiSettings = observableUiSettings, + ) } } } \ No newline at end of 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 new file mode 100644 index 000000000000..005c5004377f --- /dev/null +++ b/plugins/kotlin/refactorings/kotlin.refactorings.move.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/move/ui/ObservableUiSettings.kt @@ -0,0 +1,33 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. + +package org.jetbrains.kotlin.idea.k2.refactoring.move.ui + +import com.intellij.openapi.observable.properties.ObservableBooleanProperty + +interface ObservableUiSettings : K2MoveModelObservableSettings { + fun registerK2MoveModelSettings(settings: K2MoveModelObservableSettings) +} + +interface K2MoveModelObservableSettings { + val searchReferencesObservable: ObservableBooleanProperty + val searchForTextObservable: ObservableBooleanProperty + val searchInCommentsObservable: ObservableBooleanProperty + val mppDeclarationsObservable: ObservableBooleanProperty +} + +internal class ObservableUiSettingsImpl : ObservableUiSettings { + private lateinit var k2MoveModelSettings: K2MoveModelObservableSettings + + 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 fun registerK2MoveModelSettings(settings: K2MoveModelObservableSettings) { + k2MoveModelSettings = settings + } +}