diff --git a/idea-plugin/build.gradle.kts b/idea-plugin/build.gradle.kts index 63a14fd5285..7f0260fa76a 100644 --- a/idea-plugin/build.gradle.kts +++ b/idea-plugin/build.gradle.kts @@ -24,8 +24,8 @@ dependencies { implementation("org.jetbrains.compose:preview-rpc") intellijPlatform { + @Suppress("DEPRECATION") intellijIdeaCommunity(libs.versions.idea) - instrumentationTools() pluginVerifier() bundledPlugins("com.intellij.java", "org.jetbrains.kotlin", "com.intellij.gradle") @@ -34,9 +34,9 @@ dependencies { intellijPlatform { pluginConfiguration { - name = "Compose Multiplatform IDE Support" + name = "Compose Multiplatform for Desktop IDE Support" ideaVersion { - sinceBuild = "242.20224" + sinceBuild = "252.26199" untilBuild = provider { null } } } diff --git a/idea-plugin/gradle/libs.versions.toml b/idea-plugin/gradle/libs.versions.toml index b4f88efcb38..24c7e96f089 100644 --- a/idea-plugin/gradle/libs.versions.toml +++ b/idea-plugin/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] -kotlin = "1.9.23" -ideaPlugin = "2.1.0" -idea = "2024.2.1" +kotlin = "2.1.21" +ideaPlugin = "2.9.0" +idea = "2025.2.2" changelog = "2.2.0" [plugins] diff --git a/idea-plugin/src/main/kotlin/org/jetbrains/compose/ComposePluginReplacement.kt b/idea-plugin/src/main/kotlin/org/jetbrains/compose/ComposePluginReplacement.kt new file mode 100644 index 00000000000..272d6e008a1 --- /dev/null +++ b/idea-plugin/src/main/kotlin/org/jetbrains/compose/ComposePluginReplacement.kt @@ -0,0 +1,5 @@ +package org.jetbrains.compose + +import com.intellij.ide.plugins.PluginReplacement + +class ComposePluginReplacement : PluginReplacement("com.jetbrains.kmm") \ No newline at end of file diff --git a/idea-plugin/src/main/kotlin/org/jetbrains/compose/common/intellijUtils.kt b/idea-plugin/src/main/kotlin/org/jetbrains/compose/common/intellijUtils.kt deleted file mode 100644 index 68c32de2263..00000000000 --- a/idea-plugin/src/main/kotlin/org/jetbrains/compose/common/intellijUtils.kt +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. - */ - -package org.jetbrains.compose.common - -import com.intellij.execution.actions.ConfigurationContext -import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil - -internal fun ConfigurationContext.modulePath(): String? = - ExternalSystemApiUtil.getExternalProjectPath(location?.module) diff --git a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/ConfigurePreviewTaskNameCache.kt b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/ConfigurePreviewTaskNameCache.kt deleted file mode 100644 index 83cd4ad3454..00000000000 --- a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/ConfigurePreviewTaskNameCache.kt +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. - */ - -package org.jetbrains.compose.desktop.ide.preview - -import com.intellij.openapi.externalSystem.model.DataNode -import com.intellij.openapi.externalSystem.model.ProjectKeys -import com.intellij.openapi.externalSystem.model.project.ModuleData -import com.intellij.openapi.externalSystem.service.project.ProjectDataManager -import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil -import com.intellij.openapi.module.Module -import com.intellij.openapi.project.Project -import com.intellij.util.concurrency.annotations.RequiresReadLock -import org.jetbrains.plugins.gradle.settings.GradleSettings -import org.jetbrains.plugins.gradle.util.GradleConstants -import java.util.Locale - -internal val DEFAULT_CONFIGURE_PREVIEW_TASK_NAME = "configureDesktopPreview" - -internal interface ConfigurePreviewTaskNameProvider { - @RequiresReadLock - fun configurePreviewTaskNameOrNull(module: Module): String? -} - -internal class ConfigurePreviewTaskNameProviderImpl : ConfigurePreviewTaskNameProvider { - @RequiresReadLock - override fun configurePreviewTaskNameOrNull(module: Module): String? { - val modulePath = ExternalSystemApiUtil.getExternalProjectPath(module) ?: return null - val moduleNode = moduleDataNodeOrNull(module.project, modulePath) - if (moduleNode != null) { - val target = ExternalSystemApiUtil.getChildren(moduleNode, kotlinTargetDataKey).singleOrNull() - if (target != null) { - return previewTaskName(target.data.externalName) - } - } - - return null - } - - private fun previewTaskName(targetName: String = ""): String { - val capitalizedTargetName = - targetName.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } - return "$DEFAULT_CONFIGURE_PREVIEW_TASK_NAME$capitalizedTargetName" - } - - private fun moduleDataNodeOrNull(project: Project, modulePath: String): DataNode? { - val projectDataManager = ProjectDataManager.getInstance() - for (settings in GradleSettings.getInstance(project).linkedProjectsSettings) { - val projectInfo = projectDataManager.getExternalProjectData(project, GradleConstants.SYSTEM_ID, settings.externalProjectPath) - val projectNode = projectInfo?.externalProjectStructure ?: continue - val moduleNodes = ExternalSystemApiUtil.getChildren(projectNode, ProjectKeys.MODULE) - for (moduleNode in moduleNodes) { - val externalProjectPath = moduleNode.data.linkedExternalProjectPath - if (externalProjectPath == modulePath) { - return moduleNode - } - } - } - return null - } -} - -internal class ConfigurePreviewTaskNameCache( - private val provider: ConfigurePreviewTaskNameProvider -) : ConfigurePreviewTaskNameProvider { - private var cachedModuleId: String? = null - private var cachedTaskName: String? = null - - @RequiresReadLock - override fun configurePreviewTaskNameOrNull(module: Module): String? { - val externalProjectPath = ExternalSystemApiUtil.getExternalProjectPath(module) ?: return null - val moduleId = "$externalProjectPath#${module.name}" - - synchronized(this) { - if (moduleId == cachedModuleId) return cachedTaskName - } - - val taskName = provider.configurePreviewTaskNameOrNull(module) - synchronized(this) { - cachedTaskName = taskName - cachedModuleId = moduleId - } - return taskName - } - - fun invalidate() { - synchronized(this) { - cachedModuleId = null - cachedTaskName = null - } - } -} diff --git a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewActions.kt b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewActions.kt deleted file mode 100644 index 93851ff38c7..00000000000 --- a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewActions.kt +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. - */ - -package org.jetbrains.compose.desktop.ide.preview - -import com.intellij.execution.executors.DefaultRunExecutor -import com.intellij.openapi.actionSystem.AnAction -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.actionSystem.CommonDataKeys -import com.intellij.openapi.application.ReadAction -import com.intellij.openapi.components.service -import com.intellij.openapi.externalSystem.model.execution.ExternalSystemTaskExecutionSettings -import com.intellij.openapi.externalSystem.service.execution.ProgressExecutionMode -import com.intellij.openapi.externalSystem.task.TaskCallback -import com.intellij.openapi.externalSystem.util.ExternalSystemUtil.runTask -import com.intellij.openapi.project.Project -import com.intellij.openapi.util.UserDataHolderBase -import com.intellij.openapi.wm.ToolWindowManager -import org.jetbrains.plugins.gradle.settings.GradleSettings -import org.jetbrains.plugins.gradle.util.GradleConstants -import javax.swing.SwingUtilities - -class RunPreviewAction( - private val previewLocation: PreviewLocation -) : AnAction({ "Show non-interactive preview" }, PreviewIcons.RUN_PREVIEW) { - override fun actionPerformed(e: AnActionEvent) { - val project = e.project ?: return - buildPreviewViaGradle(project, previewLocation) - } -} - -internal const val PREVIEW_EDITOR_TOOLBAR_GROUP_ID = "Compose.Desktop.Preview.Editor.Toolbar" - -class RefreshOrRunPreviewAction : AnAction(PreviewIcons.COMPOSE) { - override fun actionPerformed(e: AnActionEvent) { - val project = e.project ?: return - val previewLocation = ReadAction.compute { - val editor = e.dataContext.getData(CommonDataKeys.EDITOR) - if (editor != null) { - e.presentation.isEnabled = false - parentPreviewAtCaretOrNull(editor) - } else null - } - if (previewLocation != null) { - buildPreviewViaGradle(project, previewLocation) - } - } -} - -private fun buildPreviewViaGradle(project: Project, previewLocation: PreviewLocation) { - val previewToolWindow = ToolWindowManager.getInstance(project).getToolWindow("Desktop Preview") - previewToolWindow?.setAvailable(true) - - val gradleVmOptions = GradleSettings.getInstance(project).gradleVmOptions - val settings = ExternalSystemTaskExecutionSettings() - settings.executionName = "Preview: ${previewLocation.fqName}" - settings.externalProjectPath = previewLocation.modulePath - settings.taskNames = listOf(previewLocation.taskName) - settings.vmOptions = gradleVmOptions - settings.externalSystemIdString = GradleConstants.SYSTEM_ID.id - val previewService = project.service() - val gradleCallbackPort = previewService.gradleCallbackPort - settings.scriptParameters = - listOf( - "-Pcompose.desktop.preview.target=${previewLocation.fqName}", - "-Pcompose.desktop.preview.ide.port=$gradleCallbackPort" - ).joinToString(" ") - SwingUtilities.invokeLater { - ToolWindowManager.getInstance(project).getToolWindow("Desktop Preview")?.activate { - previewService.buildStarted() - } - } - runTask( - settings, - DefaultRunExecutor.EXECUTOR_ID, - project, - GradleConstants.SYSTEM_ID, - object : TaskCallback { - override fun onSuccess() { - previewService.buildFinished(success = true) - } - override fun onFailure() { - previewService.buildFinished(success = false) - } - }, - ProgressExecutionMode.IN_BACKGROUND_ASYNC, - false, - UserDataHolderBase() - ) -} diff --git a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewEntryPoint.kt b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewEntryPoint.kt deleted file mode 100644 index 1d6c274c8b8..00000000000 --- a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewEntryPoint.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jetbrains.compose.desktop.ide.preview - -import com.intellij.codeInspection.reference.EntryPoint -import com.intellij.codeInspection.reference.RefElement -import com.intellij.configurationStore.deserializeInto -import com.intellij.configurationStore.serializeObjectInto -import com.intellij.psi.PsiElement -import com.intellij.psi.PsiMethod -import org.jdom.Element - -/** - * [EntryPoint] implementation to mark `@Preview` functions as entry points and avoid them being flagged as unused. - * - * Based on - * com.android.tools.idea.compose.preview.PreviewEntryPoint from AOSP - * with modifications - */ -class PreviewEntryPoint : EntryPoint() { - private var ADD_PREVIEW_TO_ENTRIES: Boolean = true - - override fun isEntryPoint(refElement: RefElement, psiElement: PsiElement): Boolean = isEntryPoint(psiElement) - - override fun isEntryPoint(psiElement: PsiElement): Boolean = - psiElement is PsiMethod && psiElement.hasAnnotation(DESKTOP_PREVIEW_ANNOTATION_FQN) - - override fun readExternal(element: Element) = element.deserializeInto(this) - - override fun writeExternal(element: Element) { - serializeObjectInto(this, element) - } - - override fun getDisplayName(): String = "Compose Preview" - - override fun isSelected(): Boolean = ADD_PREVIEW_TO_ENTRIES - - override fun setSelected(selected: Boolean) { - this.ADD_PREVIEW_TO_ENTRIES = selected - } -} \ No newline at end of file diff --git a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewFloatingToolbarProvider.kt b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewFloatingToolbarProvider.kt deleted file mode 100644 index e8a8f3543c6..00000000000 --- a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewFloatingToolbarProvider.kt +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. - */ - -package org.jetbrains.compose.desktop.ide.preview - -import com.intellij.openapi.Disposable -import com.intellij.openapi.actionSystem.CommonDataKeys -import com.intellij.openapi.actionSystem.DataContext -import com.intellij.openapi.diff.impl.DiffUtil -import com.intellij.openapi.editor.Editor -import com.intellij.openapi.editor.event.CaretEvent -import com.intellij.openapi.editor.event.CaretListener -import com.intellij.openapi.editor.toolbar.floating.AbstractFloatingToolbarProvider -import com.intellij.openapi.editor.toolbar.floating.FloatingToolbarComponent -import com.intellij.openapi.fileEditor.FileDocumentManager -import com.intellij.openapi.project.Project -import com.intellij.testFramework.LightVirtualFileBase -import com.intellij.util.concurrency.AppExecutorUtil -import org.jetbrains.kotlin.idea.KotlinFileType - -class PreviewFloatingToolbarProvider : AbstractFloatingToolbarProvider(PREVIEW_EDITOR_TOOLBAR_GROUP_ID) { - override val autoHideable = false - - // todo: disable if not in Compose JVM module - override fun register(dataContext: DataContext, component: FloatingToolbarComponent, parentDisposable: Disposable) { - val editor = dataContext.getData(CommonDataKeys.EDITOR) ?: return - if (isInsideMainKtEditor(editor)) { - registerComponent(component, editor, parentDisposable) - } - } - - private fun registerComponent( - component: FloatingToolbarComponent, - editor: Editor, - parentDisposable: Disposable - ) { - val project = editor.project - if (project != null && isPreviewCompatible(project)) { - val listener = PreviewEditorToolbarVisibilityUpdater(component, project, editor) - editor.caretModel.addCaretListener(listener, parentDisposable) - } - } -} - -internal class PreviewEditorToolbarVisibilityUpdater( - private val toolbar: FloatingToolbarComponent, - private val project: Project, - private val editor: Editor -) : CaretListener { - override fun caretPositionChanged(event: CaretEvent) { - runNonBlocking { updateVisibility() } - .inSmartMode(project) - .submit(AppExecutorUtil.getAppExecutorService()) - } - - private fun updateVisibility() { - if (!editor.isDisposed) { - val parentPreviewFun = parentPreviewAtCaretOrNull(editor) - if (parentPreviewFun != null) { - toolbar.scheduleShow() - } else { - toolbar.scheduleHide() - } - } - } -} - -private fun isInsideMainKtEditor(editor: Editor): Boolean = - !DiffUtil.isDiffEditor(editor) && editor.isKtFileEditor() - -private fun Editor.isKtFileEditor(): Boolean { - val documentManager = FileDocumentManager.getInstance() - val virtualFile = documentManager.getFile(document) ?: return false - return virtualFile !is LightVirtualFileBase - && virtualFile.isValid - && virtualFile.fileType == KotlinFileType.INSTANCE -} \ No newline at end of file diff --git a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewIcons.kt b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewIcons.kt deleted file mode 100644 index acdba102f13..00000000000 --- a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewIcons.kt +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. - */ - -package org.jetbrains.compose.desktop.ide.preview - -import com.intellij.openapi.util.IconLoader - -object PreviewIcons { - private fun load(path: String) = IconLoader.getIcon(path, PreviewIcons::class.java) - - val COMPOSE = load("/icons/compose/compose.svg") - val RUN_PREVIEW = load("/icons/compose/runPreview.svg") -} \ No newline at end of file diff --git a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewLocation.kt b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewLocation.kt deleted file mode 100644 index 8c801ca03a5..00000000000 --- a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewLocation.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. - */ - -package org.jetbrains.compose.desktop.ide.preview - -import com.intellij.openapi.components.service -import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil -import com.intellij.openapi.roots.ProjectFileIndex -import com.intellij.util.concurrency.annotations.RequiresReadLock -import org.jetbrains.kotlin.psi.KtNamedFunction - -data class PreviewLocation(val fqName: String, val modulePath: String, val taskName: String) - -@RequiresReadLock -internal fun KtNamedFunction.asPreviewFunctionOrNull(): PreviewLocation? { - if (!isValidComposablePreviewFunction()) return null - - val fqName = composePreviewFunctionFqn() - val module = ProjectFileIndex.getInstance(project).getModuleForFile(containingFile.virtualFile) - if (module == null || module.isDisposed) return null - - val service = project.service() - val previewTaskName = service.configurePreviewTaskNameOrNull(module) ?: DEFAULT_CONFIGURE_PREVIEW_TASK_NAME - val modulePath = ExternalSystemApiUtil.getExternalProjectPath(module) ?: return null - return PreviewLocation(fqName = fqName, modulePath = modulePath, taskName = previewTaskName) -} \ No newline at end of file diff --git a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewMessages.kt b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewMessages.kt deleted file mode 100644 index a7000c37c72..00000000000 --- a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewMessages.kt +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. - */ - -package org.jetbrains.compose.desktop.ide.preview - -internal object PreviewMessages { - fun runPreview(name: String): String = "Preview $name" -} \ No newline at end of file diff --git a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewRootLogger.kt b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewRootLogger.kt deleted file mode 100644 index 300ead3ad0a..00000000000 --- a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewRootLogger.kt +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. - */ - -package org.jetbrains.compose.desktop.ide.preview - -import com.intellij.openapi.diagnostic.Logger - -val LOG = Logger.getInstance(PreviewRootLogger::class.java) -private class PreviewRootLogger \ No newline at end of file diff --git a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewRunLineMarkerContributor.kt b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewRunLineMarkerContributor.kt deleted file mode 100644 index a8aefef758d..00000000000 --- a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewRunLineMarkerContributor.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jetbrains.compose.desktop.ide.preview - -import com.intellij.execution.lineMarker.RunLineMarkerContributor -import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil -import com.intellij.psi.PsiElement -import com.intellij.psi.impl.source.tree.LeafPsiElement -import org.jetbrains.kotlin.idea.util.projectStructure.module -import org.jetbrains.kotlin.lexer.KtTokens -import org.jetbrains.kotlin.psi.KtNamedFunction - -/** - * Based on - * com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunLineMarkerContributor from AOSP - * with modifications - */ -class PreviewRunLineMarkerContributor : RunLineMarkerContributor() { - override fun getInfo(element: PsiElement): Info? { - // Marker should be in a single LeafPsiElement. We choose the identifier and return null for other elements within the function. - if (element !is LeafPsiElement) return null - if (element.node.elementType != KtTokens.IDENTIFIER) return null - - return when (val parent = element.parent) { - is KtNamedFunction -> { - val previewFunction = parent.asPreviewFunctionOrNull() ?: return null - val actions = arrayOf(RunPreviewAction(previewFunction)) - Info(PreviewIcons.COMPOSE, actions) { PreviewMessages.runPreview(parent.name!!) } - } - else -> null - } - } -} diff --git a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewStateService.kt b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewStateService.kt deleted file mode 100644 index 6e0b6dd399b..00000000000 --- a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewStateService.kt +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. - */ - -package org.jetbrains.compose.desktop.ide.preview - -import com.intellij.openapi.Disposable -import com.intellij.openapi.components.Service -import com.intellij.openapi.externalSystem.model.task.* -import com.intellij.openapi.externalSystem.service.notification.ExternalSystemProgressNotificationManager -import com.intellij.openapi.module.Module -import com.intellij.openapi.util.Disposer -import com.intellij.ui.components.JBLoadingPanel -import com.intellij.util.concurrency.annotations.RequiresReadLock -import org.jetbrains.compose.desktop.ide.preview.ui.PreviewPanel -import org.jetbrains.compose.desktop.ui.tooling.preview.rpc.* -import org.jetbrains.plugins.gradle.util.GradleConstants -import javax.swing.JComponent -import javax.swing.event.AncestorEvent -import javax.swing.event.AncestorListener - -@Service(Service.Level.PROJECT) -class PreviewStateService : Disposable { - private val previewListener = CompositePreviewListener() - private val previewManager: PreviewManager = PreviewManagerImpl(previewListener) - val gradleCallbackPort: Int - get() = previewManager.gradleCallbackPort - private val configurePreviewTaskNameCache = - ConfigurePreviewTaskNameCache(ConfigurePreviewTaskNameProviderImpl()) - - init { - val projectRefreshListener = ConfigurePreviewTaskNameCacheInvalidator(configurePreviewTaskNameCache) - ExternalSystemProgressNotificationManager.getInstance() - .addNotificationListener(projectRefreshListener, this) - } - - @RequiresReadLock - internal fun configurePreviewTaskNameOrNull(module: Module): String? = - configurePreviewTaskNameCache.configurePreviewTaskNameOrNull(module) - - override fun dispose() { - previewManager.close() - configurePreviewTaskNameCache.invalidate() - } - - internal fun registerPreviewPanels( - previewPanel: PreviewPanel, - loadingPanel: JBLoadingPanel - ) { - val previewResizeListener = PreviewResizeListener(previewManager) - previewPanel.addAncestorListener(previewResizeListener) - Disposer.register(this) { previewPanel.removeAncestorListener(previewResizeListener) } - - previewListener.addListener(PreviewPanelUpdater(previewPanel)) - previewListener.addListener(LoadingPanelUpdater(loadingPanel)) - } - - internal fun buildStarted() { - previewListener.onNewBuildRequest() - } - - internal fun buildFinished(success: Boolean) { - previewListener.onFinishedBuild(success) - } -} - -private class PreviewResizeListener(private val previewManager: PreviewManager) : AncestorListener { - private fun updateFrameSize(c: JComponent) { - val frameConfig = FrameConfig( - width = c.width, - height = c.height, - scale = c.graphicsConfiguration.defaultTransform.scaleX - ) - previewManager.updateFrameConfig(frameConfig) - } - - override fun ancestorAdded(event: AncestorEvent) { - updateFrameSize(event.component) - } - - override fun ancestorRemoved(event: AncestorEvent) { - } - - override fun ancestorMoved(event: AncestorEvent) { - updateFrameSize(event.component) - } -} - -private class PreviewPanelUpdater(private val panel: PreviewPanel) : PreviewListenerBase() { - override fun onRenderedFrame(frame: RenderedFrame) { - panel.previewImage(frame.image, frame.dimension) - } - - override fun onError(error: String) { - panel.error(error) - } -} - -private class LoadingPanelUpdater(private val panel: JBLoadingPanel) : PreviewListenerBase() { - override fun onNewBuildRequest() { - panel.setLoadingText("Building project") - panel.startLoading() - } - - override fun onFinishedBuild(success: Boolean) { - panel.stopLoading() - } - - override fun onNewRenderRequest(previewRequest: FrameRequest) { - panel.setLoadingText("Rendering preview") - panel.startLoading() - } - - override fun onRenderedFrame(frame: RenderedFrame) { - panel.stopLoading() - } -} - -// ExternalSystemTaskNotificationListenerAdapter is used, -// because ExternalSystemTaskNotificationListener interface's API -// was changed between 2020.3 and 2021.1, so a direct implementation -// would not work with both 2020.3 and 2021.1 -private class ConfigurePreviewTaskNameCacheInvalidator( - private val configurePreviewTaskNameCache: ConfigurePreviewTaskNameCache -) : ExternalSystemTaskNotificationListenerAdapter(null) { - override fun onStart(id: ExternalSystemTaskId, workingDir: String?) { - if ( - id.projectSystemId == GradleConstants.SYSTEM_ID && - id.type == ExternalSystemTaskType.RESOLVE_PROJECT - ) { - configurePreviewTaskNameCache.invalidate() - } - } -} diff --git a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewToolWindow.kt b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewToolWindow.kt deleted file mode 100644 index d4c0606b22e..00000000000 --- a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewToolWindow.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. - */ -package org.jetbrains.compose.desktop.ide.preview - -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.components.service -import com.intellij.openapi.project.DumbAware -import com.intellij.openapi.project.Project -import com.intellij.openapi.wm.ToolWindow -import com.intellij.openapi.wm.ToolWindowFactory -import com.intellij.ui.components.JBLoadingPanel -import java.awt.BorderLayout -import org.jetbrains.compose.desktop.ide.preview.ui.PreviewPanel - -class PreviewToolWindow : ToolWindowFactory, DumbAware { - override suspend fun isApplicableAsync(project: Project): Boolean = isPreviewCompatible(project) - - override fun init(toolWindow: ToolWindow) { - ApplicationManager.getApplication().invokeLater { toolWindow.setIcon(PreviewIcons.COMPOSE) } - } - - override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) { - toolWindow.contentManager.let { content -> - val panel = PreviewPanel(project) - val loadingPanel = JBLoadingPanel(BorderLayout(), toolWindow.disposable) - loadingPanel.add(panel, BorderLayout.CENTER) - content.addContent(content.factory.createContent(loadingPanel, null, false)) - project.service().registerPreviewPanels(panel, loadingPanel) - } - } - - // don't show the toolwindow until a preview is requested - override fun shouldBeAvailable(project: Project): Boolean = false -} diff --git a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/editorUtils.kt b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/editorUtils.kt deleted file mode 100644 index e7c9b99fc16..00000000000 --- a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/editorUtils.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. - */ - -package org.jetbrains.compose.desktop.ide.preview - -import com.intellij.openapi.editor.Editor -import com.intellij.openapi.fileEditor.FileDocumentManager -import com.intellij.psi.PsiFile -import com.intellij.psi.PsiManager -import com.intellij.util.concurrency.annotations.RequiresReadLock -import org.jetbrains.kotlin.idea.KotlinFileType -import org.jetbrains.kotlin.psi.KtNamedFunction - -@RequiresReadLock -internal fun parentPreviewAtCaretOrNull(editor: Editor): PreviewLocation? { - val caretModel = editor.caretModel - val psiFile = kotlinPsiFile(editor) - if (psiFile != null) { - var node = psiFile.findElementAt(caretModel.offset) - while (node != null) { - val previewFunction = (node as? KtNamedFunction)?.asPreviewFunctionOrNull() - if (previewFunction != null) { - return previewFunction - } - node = node.parent - } - } - - return null -} - -private fun kotlinPsiFile(editor: Editor): PsiFile? { - val project = editor.project ?: return null - val documentManager = FileDocumentManager.getInstance() - val file = documentManager.getFile(editor.document) - return if (file != null && file.fileType is KotlinFileType) { - PsiManager.getInstance(project).findFile(file) - } else null -} \ No newline at end of file diff --git a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/ijCompatibilityUtils.kt b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/ijCompatibilityUtils.kt deleted file mode 100644 index 7bc501682b1..00000000000 --- a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/ijCompatibilityUtils.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. - */ - -package org.jetbrains.compose.desktop.ide.preview - -import com.intellij.ide.lightEdit.LightEdit -import com.intellij.openapi.application.NonBlockingReadAction -import com.intellij.openapi.application.ReadAction -import com.intellij.openapi.externalSystem.model.Key -import com.intellij.openapi.externalSystem.model.project.AbstractNamedData -import com.intellij.openapi.project.Project -import java.lang.reflect.Modifier -import java.util.concurrent.Callable - -// todo: filter only Compose projects -internal fun isPreviewCompatible(project: Project): Boolean = - !LightEdit.owns(project) - -internal val kotlinTargetDataKey: Key = run { - val kotlinTargetDataClass = try { - Class.forName("org.jetbrains.kotlin.idea.gradle.configuration.KotlinTargetData") - } catch (e: ClassNotFoundException) { - try { - Class.forName("org.jetbrains.kotlin.idea.configuration.KotlinTargetData") - } catch (e: ClassNotFoundException) { - error("Could not find 'KotlinTargetData' class") - } - } - val companionField = kotlinTargetDataClass.fields.firstOrNull { Modifier.isStatic(it.modifiers) && it.name == "Companion" } - ?: error("'${kotlinTargetDataClass.canonicalName}.Companion") - val companionInstance = companionField.get(kotlinTargetDataClass) - val companionClass = companionInstance.javaClass - val getKeyMethod = companionClass.methods.firstOrNull { it.name == "getKEY" } - ?: error("Cannot find '${kotlinTargetDataClass.canonicalName}.Companion.getKEY'") - @Suppress("UNCHECKED_CAST") - getKeyMethod.invoke(companionInstance) as Key -} - -internal inline fun runNonBlocking(crossinline fn: () -> Unit): NonBlockingReadAction = - ReadAction.nonBlocking(Callable { - fn() - null - }) \ No newline at end of file diff --git a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/locationUtils.kt b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/locationUtils.kt deleted file mode 100644 index fec6f8fd538..00000000000 --- a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/locationUtils.kt +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jetbrains.compose.desktop.ide.preview - -import com.intellij.openapi.roots.ProjectRootModificationTracker -import com.intellij.psi.util.CachedValueProvider -import com.intellij.psi.util.CachedValuesManager -import com.intellij.psi.util.parentOfType -import com.intellij.util.concurrency.annotations.RequiresReadLock -import org.jetbrains.kotlin.analysis.api.analyze -import org.jetbrains.kotlin.analysis.api.symbols.KaClassLikeSymbol -import org.jetbrains.kotlin.asJava.findFacadeClass -import org.jetbrains.kotlin.name.ClassId -import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.psi.KtClass -import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.psi.KtNamedFunction -import org.jetbrains.kotlin.psi.allConstructors -import org.jetbrains.kotlin.psi.psiUtil.containingClass - -internal const val DESKTOP_PREVIEW_ANNOTATION_FQN = - "androidx.compose.desktop.ui.tooling.preview.Preview" -internal const val COMPOSABLE_FQ_NAME = "androidx.compose.runtime.Composable" - -private val ComposableAnnotationClassId = ClassId.topLevel(FqName(COMPOSABLE_FQ_NAME)) -private val DesktopPreviewAnnotationClassId = - ClassId.topLevel(FqName(DESKTOP_PREVIEW_ANNOTATION_FQN)) - -/** - * Utils based on functions from AOSP, taken from - * tools/adt/idea/compose-designer/src/com/android/tools/idea/compose/preview/util/PreviewElement.kt - */ - -/** - * Returns whether a `@Composable` [DESKTOP_PREVIEW_ANNOTATION_FQN] is defined in a valid location, - * which can be either: - * 1. Top-level functions - * 2. Non-nested functions defined in top-level classes that have a default (no parameter) - * constructor - */ -private fun KtNamedFunction.isValidPreviewLocation(): Boolean { - if (valueParameters.isNotEmpty()) return false - if (receiverTypeReference != null) return false - - if (isTopLevel) return true - - if (parentOfType() == null) { - // This is not a nested method - val containingClass = containingClass() - if (containingClass != null) { - // We allow functions that are not top level defined in top level classes that have a - // default (no parameter) constructor. - if (containingClass.isTopLevel() && containingClass.hasDefaultConstructor()) { - return true - } - } - } - return false -} - -/** - * Computes the qualified name of the class containing this [KtNamedFunction]. - * - * For functions defined within a Kotlin class, returns the qualified name of that class. For - * top-level functions, returns the JVM name of the Java facade class generated instead. - */ -internal fun KtNamedFunction.getClassName(): String? = - if (isTopLevel) ((parent as? KtFile)?.findFacadeClass())?.qualifiedName - else parentOfType()?.getQualifiedName() - -/** - * Computes the qualified name for a Kotlin Class. Returns null if the class is a kotlin built-in. - */ -private fun KtClass.getQualifiedName(): String? = - analyze(this) { - val classSymbol = symbol - return when { - classSymbol !is KaClassLikeSymbol -> null - classSymbol.classId.isKotlinPackage() -> null - else -> classSymbol.classId?.asFqNameString() - } - } - -private fun ClassId?.isKotlinPackage() = - this != null && startsWith(org.jetbrains.kotlin.builtins.StandardNames.BUILT_INS_PACKAGE_NAME) - -private fun KtClass.hasDefaultConstructor() = - allConstructors.isEmpty().or(allConstructors.any { it.valueParameters.isEmpty() }) - -internal fun KtNamedFunction.composePreviewFunctionFqn() = "${getClassName()}.${name}" - -@RequiresReadLock -internal fun KtNamedFunction.isValidComposablePreviewFunction(): Boolean { - fun isValidComposablePreviewImpl(): Boolean = - analyze(this) { - if (!isValidPreviewLocation()) return false - - val mySymbol = symbol - val hasComposableAnnotation = mySymbol.annotations.contains(ComposableAnnotationClassId) - val hasPreviewAnnotation = - mySymbol.annotations.contains(DesktopPreviewAnnotationClassId) - - return hasComposableAnnotation && hasPreviewAnnotation - } - - return CachedValuesManager.getCachedValue(this) { cachedResult(isValidComposablePreviewImpl()) } -} - -// based on AndroidComposePsiUtils.kt from AOSP -internal fun KtNamedFunction.isComposableFunction(): Boolean = - CachedValuesManager.getCachedValue(this) { - val hasComposableAnnotation = - analyze(this) { symbol.annotations.contains(ComposableAnnotationClassId) } - - cachedResult(hasComposableAnnotation) - } - -private fun KtNamedFunction.cachedResult(value: T) = - CachedValueProvider.Result.create( - // TODO: see if we can handle alias imports without ruining performance. - value, - this.containingKtFile, - ProjectRootModificationTracker.getInstance(project), - ) diff --git a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/ui/PreviewPanel.kt b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/ui/PreviewPanel.kt deleted file mode 100644 index 4fe1241fb40..00000000000 --- a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/ui/PreviewPanel.kt +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. - */ - -package org.jetbrains.compose.desktop.ide.preview.ui - -import com.intellij.icons.AllIcons -import com.intellij.openapi.project.Project -import com.intellij.ui.SimpleTextAttributes -import com.intellij.ui.components.JBPanel -import com.intellij.util.ui.StatusText -import java.awt.Color -import java.awt.Dimension -import java.awt.Graphics -import java.awt.image.BufferedImage -import java.util.concurrent.atomic.AtomicReference -import javax.swing.SwingUtilities - -internal class PreviewPanel(private val myProject: Project) : JBPanel() { - sealed class PreviewPanelState { - data class Image(val image: BufferedImage, val dimension: Dimension) : PreviewPanelState() - class Error(val error: String) : PreviewPanelState() - } - private val myState = AtomicReference() - private val myStatusText = object : StatusText(this) { - override fun isStatusVisible(): Boolean { - return myState.get() is PreviewPanelState.Error - } - } - - init { - SwingUtilities.invokeLater { - myStatusText.initStatusText() - } - } - - fun StatusText.initStatusText() { - clear() - appendLine( - AllIcons.General.Error, - "Preview rendering encountered an error", - SimpleTextAttributes.REGULAR_ATTRIBUTES, - null - ) - appendLine( - "Show details", - SimpleTextAttributes.LINK_ATTRIBUTES - ) { - val errorText = (myState.get() as? PreviewPanelState.Error)?.error - showTextDialog("Preview Error Details", errorText.orEmpty(), myProject) - } - } - - override fun paintComponent(g: Graphics) { - super.paintComponent(g) - - when (val state = myState.get()) { - is PreviewPanelState.Image -> { - val (image, dimension) = state - val w = dimension.width - val h = dimension.height - g.color = Color.WHITE - g.fillRect(0, 0, w, h) - g.drawImage(image, 0, 0, w, h, null) - } - is PreviewPanelState.Error -> { - myStatusText.paint(this, g) - } - } - } - - fun previewImage(image: BufferedImage, imageDimension: Dimension) { - myState.set(PreviewPanelState.Image(image, imageDimension)) - SwingUtilities.invokeLater { - repaint() - } - } - - fun error(error: String) { - myState.set(PreviewPanelState.Error(error)) - SwingUtilities.invokeLater { - repaint() - } - } - - override fun getPreferredSize(): Dimension? = - (myState.get() as? PreviewPanelState.Image)?.dimension ?: super.getPreferredSize() -} \ No newline at end of file diff --git a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/ui/uiUtils.kt b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/ui/uiUtils.kt deleted file mode 100644 index a31528f11ec..00000000000 --- a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/ui/uiUtils.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. - */ - -package org.jetbrains.compose.desktop.ide.preview.ui - -import com.intellij.openapi.project.Project -import com.intellij.openapi.ui.DialogWrapper -import com.intellij.ui.ScrollPaneFactory -import java.awt.BorderLayout -import javax.swing.JComponent -import javax.swing.JPanel -import javax.swing.JTextArea - -fun showTextDialog( - title: String, - text: String, - project: Project? = null - ) { - val wrapper: DialogWrapper = object : DialogWrapper(project, false) { - init { - init() - } - - override fun createCenterPanel(): JComponent { - val textArea = JTextArea(text).apply { - isEditable = false - rows = 40 - columns = 70 - } - return JPanel(BorderLayout()).apply { - add(ScrollPaneFactory.createScrollPane(textArea)) - } - } - } - wrapper.title = title - wrapper.show() -} diff --git a/idea-plugin/src/main/kotlin/org/jetbrains/compose/inspections/ComposeSuppressor.kt b/idea-plugin/src/main/kotlin/org/jetbrains/compose/inspections/ComposeSuppressor.kt deleted file mode 100644 index 04407d2f02b..00000000000 --- a/idea-plugin/src/main/kotlin/org/jetbrains/compose/inspections/ComposeSuppressor.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.jetbrains.compose.inspections - -import com.intellij.codeInspection.InspectionSuppressor -import com.intellij.codeInspection.SuppressQuickFix -import com.intellij.psi.PsiElement -import org.jetbrains.compose.desktop.ide.preview.isComposableFunction -import org.jetbrains.kotlin.idea.KotlinLanguage -import org.jetbrains.kotlin.lexer.KtTokens -import org.jetbrains.kotlin.psi.KtNamedFunction - -/** - * Suppress inspection that require composable function names to start with a lower case letter. - */ -class ComposeSuppressor : InspectionSuppressor { - override fun isSuppressedFor(element: PsiElement, toolId: String): Boolean { - return toolId == "FunctionName" && - element.language == KotlinLanguage.INSTANCE && - element.node.elementType == KtTokens.IDENTIFIER && - element.parent.let { it is KtNamedFunction && it.isComposableFunction() } - } - - override fun getSuppressActions(element: PsiElement?, toolId: String): Array { - return SuppressQuickFix.EMPTY_ARRAY - } -} - diff --git a/idea-plugin/src/main/kotlin/org/jetbrains/compose/web/ide/run/WebRunConfigurationProducer.kt b/idea-plugin/src/main/kotlin/org/jetbrains/compose/web/ide/run/WebRunConfigurationProducer.kt deleted file mode 100644 index d0cc3a9641b..00000000000 --- a/idea-plugin/src/main/kotlin/org/jetbrains/compose/web/ide/run/WebRunConfigurationProducer.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. - */ -package org.jetbrains.compose.web.ide.run - -import com.intellij.execution.actions.ConfigurationContext -import com.intellij.execution.actions.LazyRunConfigurationProducer -import com.intellij.execution.configurations.ConfigurationFactory -import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil -import com.intellij.openapi.util.Ref -import com.intellij.psi.PsiElement -import org.jetbrains.compose.common.modulePath -import org.jetbrains.kotlin.psi.KtNamedFunction -import org.jetbrains.plugins.gradle.service.execution.GradleExternalTaskConfigurationType -import org.jetbrains.plugins.gradle.service.execution.GradleRunConfiguration -import org.jetbrains.plugins.gradle.util.GradleConstants - -class WebRunConfigurationProducer : LazyRunConfigurationProducer() { - override fun getConfigurationFactory(): ConfigurationFactory = - GradleExternalTaskConfigurationType.getInstance().factory - - override fun isConfigurationFromContext( - configuration: GradleRunConfiguration, - context: ConfigurationContext - ): Boolean { - val mainFun = context.jsMainOrNull ?: return false - return configuration.run { - name == mainFun.name!! - && settings.externalProjectPath == context.modulePath() - && settings.taskNames.contains(jsRunTaskName) - } - } - - override fun setupConfigurationFromContext( - configuration: GradleRunConfiguration, - context: ConfigurationContext, - sourceElement: Ref - ): Boolean { - val mainFun = context.jsMainOrNull ?: return false - configuration.apply { - name = mainFun.name!! - settings.taskNames.add(jsRunTaskName) - settings.externalProjectPath = - ExternalSystemApiUtil.getExternalProjectPath(context.location?.module) - } - return true - } - - companion object { - private const val jsRunTaskName = "jsBrowserDevelopmentRun" - } -} - -private val ConfigurationContext.jsMainOrNull: KtNamedFunction? - get() = psiLocation?.parent?.getAsJsMainFunctionOrNull() \ No newline at end of file diff --git a/idea-plugin/src/main/kotlin/org/jetbrains/compose/web/ide/run/WebRunLineMarkerContributor.kt b/idea-plugin/src/main/kotlin/org/jetbrains/compose/web/ide/run/WebRunLineMarkerContributor.kt deleted file mode 100644 index 14c1a0d1ea8..00000000000 --- a/idea-plugin/src/main/kotlin/org/jetbrains/compose/web/ide/run/WebRunLineMarkerContributor.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. - */ -package org.jetbrains.compose.web.ide.run - -import com.intellij.execution.lineMarker.ExecutorAction -import com.intellij.execution.lineMarker.RunLineMarkerContributor -import com.intellij.icons.AllIcons -import com.intellij.psi.PsiElement -import com.intellij.psi.impl.source.tree.LeafPsiElement -import org.jetbrains.kotlin.lexer.KtTokens - -class WebRunLineMarkerContributor : RunLineMarkerContributor() { - override fun getInfo(element: PsiElement): Info? { - if (element !is LeafPsiElement) return null - if (element.node.elementType != KtTokens.IDENTIFIER) return null - if (element.parent.getAsJsMainFunctionOrNull() == null) return null - - val icon = AllIcons.RunConfigurations.TestState.Run - return Info(icon, arrayOf(ExecutorAction.getActions()[0])) - } -} diff --git a/idea-plugin/src/main/kotlin/org/jetbrains/compose/web/ide/run/psiUtils.kt b/idea-plugin/src/main/kotlin/org/jetbrains/compose/web/ide/run/psiUtils.kt deleted file mode 100644 index 3e9b3ec8cd1..00000000000 --- a/idea-plugin/src/main/kotlin/org/jetbrains/compose/web/ide/run/psiUtils.kt +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. - */ - -package org.jetbrains.compose.web.ide.run - -import com.intellij.psi.PsiElement -import com.intellij.util.concurrency.annotations.RequiresReadLock -import org.jetbrains.kotlin.analysis.api.KaSession -import org.jetbrains.kotlin.analysis.api.analyze -import org.jetbrains.kotlin.analysis.api.symbols.KaNamedFunctionSymbol -import org.jetbrains.kotlin.analysis.api.types.KaClassType -import org.jetbrains.kotlin.analysis.api.types.KaType -import org.jetbrains.kotlin.analysis.api.types.KaTypeNullability -import org.jetbrains.kotlin.config.LanguageFeature -import org.jetbrains.kotlin.idea.base.facet.platform.platform -import org.jetbrains.kotlin.idea.base.projectStructure.languageVersionSettings -import org.jetbrains.kotlin.idea.base.psi.KotlinPsiHeuristics -import org.jetbrains.kotlin.idea.base.util.module -import org.jetbrains.kotlin.name.StandardClassIds -import org.jetbrains.kotlin.platform.js.JsPlatforms -import org.jetbrains.kotlin.psi.KtNamedFunction -import org.jetbrains.kotlin.types.Variance - -internal fun PsiElement.getAsJsMainFunctionOrNull(): KtNamedFunction? = - (this as? KtNamedFunction)?.takeIf { it.isValidJsMain() } - -internal fun KtNamedFunction.isValidJsMain(): Boolean = isTopLevel && isJsPlatform() && isMainFun() - -internal fun KtNamedFunction.isJsPlatform(): Boolean = - module?.platform?.let { platform -> platform in JsPlatforms.allJsPlatforms } == true - -internal fun KtNamedFunction.isMainFun(): Boolean = - isMainFromPsiOnly(this) && isMainFromAnalysis(this) - -////////////////////////////////////////////////////////////////// -// Copied and simplified from PsiOnlyKotlinMainFunctionDetector // -////////////////////////////////////////////////////////////////// -@RequiresReadLock -private fun isMainFromPsiOnly(function: KtNamedFunction): Boolean { - if (function.isLocal || function.typeParameters.isNotEmpty()) { - return false - } - - val isTopLevel = function.isTopLevel - val parameterCount = - function.valueParameters.size + (if (function.receiverTypeReference != null) 1 else 0) - - if (parameterCount == 0) { - if (!isTopLevel) { - return false - } - - if ( - !function.languageVersionSettings.supportsFeature( - LanguageFeature.ExtendedMainConvention - ) - ) { - return false - } - } else if (parameterCount == 1 && !isMainCheckParameter(function)) { - return false - } else { - return false - } - - if ((KotlinPsiHeuristics.findJvmName(function) ?: function.name) != "main") { - return false - } - - if (!isTopLevel && !KotlinPsiHeuristics.hasJvmStaticAnnotation(function)) { - return false - } - - val returnTypeReference = function.typeReference - return !(returnTypeReference != null && - !KotlinPsiHeuristics.typeMatches(returnTypeReference, StandardClassIds.Unit)) -} - -private fun isMainCheckParameter(function: KtNamedFunction): Boolean { - val receiverTypeReference = function.receiverTypeReference - if (receiverTypeReference != null) { - return KotlinPsiHeuristics.typeMatches( - receiverTypeReference, - StandardClassIds.Array, - StandardClassIds.String, - ) - } - - val parameter = function.valueParameters.singleOrNull() ?: return false - val parameterTypeReference = parameter.typeReference ?: return false - - return when { - parameter.isVarArg -> - KotlinPsiHeuristics.typeMatches(parameterTypeReference, StandardClassIds.String) - else -> - KotlinPsiHeuristics.typeMatches( - parameterTypeReference, - StandardClassIds.Array, - StandardClassIds.String, - ) - } -} - -////////////////////////////////////////////////////////////////////// -// Copied and simplified from SymbolBasedKotlinMainFunctionDetector // -////////////////////////////////////////////////////////////////////// -private fun isMainFromAnalysis(function: KtNamedFunction): Boolean { - if (function.isLocal || function.typeParameters.isNotEmpty()) { - return false - } - - val supportsExtendedMainConvention = - function.languageVersionSettings.supportsFeature(LanguageFeature.ExtendedMainConvention) - - val isTopLevel = function.isTopLevel - val parameterCount = - function.valueParameters.size + (if (function.receiverTypeReference != null) 1 else 0) - - if (parameterCount == 0) { - if (!isTopLevel || !supportsExtendedMainConvention) { - return false - } - } else if (parameterCount > 1) { - return false - } - - analyze(function) { - if (parameterCount == 1) { - val parameterTypeReference = - function.receiverTypeReference - ?: function.valueParameters[0].typeReference - ?: return false - - val parameterType = parameterTypeReference.type - if ( - !parameterType.isResolvedClassType() || - !parameterType.isSubtypeOf(buildMainParameterType()) - ) { - return false - } - } - - val functionSymbol = function.symbol - if (functionSymbol !is KaNamedFunctionSymbol) { - return false - } - - if (functionSymbol.name.identifier != "main") { - return false - } - - if (!function.returnType.isUnitType) { - return false - } - } - return true -} - -private fun KaSession.buildMainParameterType(): KaType = - buildClassType(StandardClassIds.Array) { - val argumentType = - buildClassType(StandardClassIds.String) { nullability = KaTypeNullability.NON_NULLABLE } - - argument(argumentType, Variance.OUT_VARIANCE) - nullability = KaTypeNullability.NULLABLE - } - -private fun KaType.isResolvedClassType(): Boolean = - when (this) { - is KaClassType -> typeArguments.mapNotNull { it.type }.all { it.isResolvedClassType() } - else -> false - } diff --git a/idea-plugin/src/main/resources/META-INF/plugin.xml b/idea-plugin/src/main/resources/META-INF/plugin.xml index 6accf89e542..0ff72a848dc 100644 --- a/idea-plugin/src/main/resources/META-INF/plugin.xml +++ b/idea-plugin/src/main/resources/META-INF/plugin.xml @@ -1,16 +1,12 @@ org.jetbrains.compose.desktop.ide - Compose Multiplatform IDE Support + Compose Multiplatform for Desktop IDE Support JetBrains Compose for Desktop - applications. - The main feature at the moment is IDE preview of composable functions - marked by @Preview annotation. +

⚠️ This plugin is in no longer supported

+

Its functionality is integrated into the new Kotlin Multiplatform plugin.

]]> -
@@ -27,33 +23,9 @@ + com.jetbrains.kmm - - - - - - - - - - - - - + - - - - -
diff --git a/idea-plugin/src/main/resources/icons/compose/compose.svg b/idea-plugin/src/main/resources/icons/compose/compose.svg deleted file mode 100644 index 2154d497bd6..00000000000 --- a/idea-plugin/src/main/resources/icons/compose/compose.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/idea-plugin/src/main/resources/icons/compose/runPreview.svg b/idea-plugin/src/main/resources/icons/compose/runPreview.svg deleted file mode 100644 index e6f910728cf..00000000000 --- a/idea-plugin/src/main/resources/icons/compose/runPreview.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - -