From 45242dc54ced6022203d6ec9c9d79c368e800b70 Mon Sep 17 00:00:00 2001 From: Alexey Tsvetkov <654232+AlexeyTsvetkov@users.noreply.github.com> Date: Mon, 5 Jul 2021 17:32:18 +0300 Subject: [PATCH] Don't create repeated run configurations for preview (#851) --- .../desktop/ide/preview/PreviewIcons.kt | 1 + .../PreviewRunConfigurationProducer.kt | 99 ------------------- .../PreviewRunLineMarkerContributor.kt | 9 +- .../desktop/ide/preview/RunPreviewAction.kt | 59 +++++++++++ .../desktop/ide/preview/locationUtils.kt | 2 + .../src/main/resources/META-INF/plugin.xml | 2 - .../resources/icons/compose/runPreview.svg | 4 + 7 files changed, 72 insertions(+), 104 deletions(-) delete mode 100644 idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewRunConfigurationProducer.kt create mode 100644 idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/RunPreviewAction.kt create mode 100644 idea-plugin/src/main/resources/icons/compose/runPreview.svg 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 index 6efc761aa3..acdba102f1 100644 --- 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 @@ -11,4 +11,5 @@ 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/PreviewRunConfigurationProducer.kt b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewRunConfigurationProducer.kt deleted file mode 100644 index 66730e14b5..0000000000 --- a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewRunConfigurationProducer.kt +++ /dev/null @@ -1,99 +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.actions.ConfigurationContext -import com.intellij.execution.actions.ConfigurationFromContext -import com.intellij.execution.actions.LazyRunConfigurationProducer -import com.intellij.execution.configurations.ConfigurationFactory -import com.intellij.openapi.components.service -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.kotlin.psi.psiUtil.getNonStrictParentOfType -import org.jetbrains.plugins.gradle.service.execution.GradleExternalTaskConfigurationType -import org.jetbrains.plugins.gradle.service.execution.GradleRunConfiguration - -/** - * Producer of [ComposePreviewRunConfiguration] for `@Composable` functions annotated with [PREVIEW_ANNOTATION_FQN]. The configuration - * created is initially named after the `@Composable` function, and its fully qualified name is properly set in the configuration. - * - * The [ConfigurationContext] where the [ComposePreviewRunConfiguration] is created from can be any descendant of the `@Composable` function - * in the PSI tree, such as its annotations, function name or even the keyword "fun". - * - * Based on com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer from AOSP - * with modifications - */ -class PreviewRunConfigurationProducer : LazyRunConfigurationProducer() { - override fun getConfigurationFactory(): ConfigurationFactory = - GradleExternalTaskConfigurationType.getInstance().factory - - override fun isConfigurationFromContext( - configuration: GradleRunConfiguration, - context: ConfigurationContext - ): Boolean { - val composeFunction = context.containingComposePreviewFunction() ?: return false - return configuration.run { - name == runConfigurationNameFor(composeFunction) - && settings.externalProjectPath == context.modulePath() - && settings.taskNames.singleOrNull() == configureDesktopPreviewTaskName - && settings.scriptParameters.split(" ").containsAll( - runConfigurationScriptParameters(composeFunction.composePreviewFunctionFqn(), context.port) - ) - } - } - - override fun setupConfigurationFromContext( - configuration: GradleRunConfiguration, - context: ConfigurationContext, - sourceElement: Ref - ): Boolean { - val composeFunction = context.containingComposePreviewFunction() ?: return false - // todo: temporary configuration? - configuration.apply { - name = runConfigurationNameFor(composeFunction) - settings.taskNames.add(configureDesktopPreviewTaskName) - settings.externalProjectPath = ExternalSystemApiUtil.getExternalProjectPath(context.location?.module) - settings.scriptParameters = - runConfigurationScriptParameters(composeFunction.composePreviewFunctionFqn(), context.port) - .joinToString(" ") - } - - return true - } -} - -private val configureDesktopPreviewTaskName = "configureDesktopPreview" - -private fun runConfigurationNameFor(function: KtNamedFunction): String = - "Compose Preview: ${function.name!!}" - -private fun runConfigurationScriptParameters(target: String, idePort: Int): List = - listOf( - "-Pcompose.desktop.preview.target=$target", - "-Pcompose.desktop.preview.ide.port=${idePort}" - ) - -private val ConfigurationContext.port: Int - get() = project.service().gradleCallbackPort - -private fun KtNamedFunction.composePreviewFunctionFqn() = "${getClassName()}.${name}" - -private fun ConfigurationContext.containingComposePreviewFunction() = - psiLocation?.let { location -> location.getNonStrictParentOfType()?.takeIf { it.isValidComposePreview() } } \ 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 index 508deb2c69..41ef1336ba 100644 --- 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 @@ -16,10 +16,11 @@ package org.jetbrains.compose.desktop.ide.preview -import com.intellij.execution.lineMarker.ExecutorAction 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 @@ -37,11 +38,13 @@ class PreviewRunLineMarkerContributor : RunLineMarkerContributor() { val parent = element.parent return when { parent is KtNamedFunction && parent.isValidComposePreview() -> { - val actions = arrayOf(ExecutorAction.getActions(0).first()) + val fqName = parent.composePreviewFunctionFqn() + val module = parent.module?.let { ExternalSystemApiUtil.getExternalProjectPath(it) } + ?: error("Could not determine module for $parent") + val actions = arrayOf(RunPreviewAction(fqName, module)) Info(PreviewIcons.COMPOSE, actions) { PreviewMessages.runPreview(parent.name!!) } } else -> null } } } - diff --git a/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/RunPreviewAction.kt b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/RunPreviewAction.kt new file mode 100644 index 0000000000..94be34e2e2 --- /dev/null +++ b/idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/RunPreviewAction.kt @@ -0,0 +1,59 @@ +/* + * 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.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.util.UserDataHolderBase +import org.jetbrains.plugins.gradle.settings.GradleSettings +import org.jetbrains.plugins.gradle.util.GradleConstants +import java.util.concurrent.CompletableFuture + +class RunPreviewAction( + private val fqName: String, + private val modulePath: String +) : AnAction({ "Show non-interactive preview" }, PreviewIcons.RUN_PREVIEW) { + override fun actionPerformed(e: AnActionEvent) { + val project = e.project!! + val gradleVmOptions = GradleSettings.getInstance(project).gradleVmOptions + val settings = ExternalSystemTaskExecutionSettings() + settings.executionName = "Preview: $fqName" + settings.externalProjectPath = modulePath + settings.taskNames = listOf("configureDesktopPreview") + settings.vmOptions = gradleVmOptions + settings.externalSystemIdString = GradleConstants.SYSTEM_ID.id + val gradleCallbackPort = project.service().gradleCallbackPort + settings.scriptParameters = + listOf( + "-Pcompose.desktop.preview.target=$fqName", + "-Pcompose.desktop.preview.ide.port=$gradleCallbackPort" + ).joinToString(" ") + val future = CompletableFuture() + runTask( + settings, + DefaultRunExecutor.EXECUTOR_ID, + project, + GradleConstants.SYSTEM_ID, + object : TaskCallback { + override fun onSuccess() { + future.complete(null) + } + override fun onFailure() { + future.completeExceptionally(RuntimeException("Preview for $fqName failed")) + } + }, + ProgressExecutionMode.IN_BACKGROUND_ASYNC, + false, + UserDataHolderBase() + ) + } +} \ 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 index 5906832b52..c2f40c676f 100644 --- 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 @@ -109,4 +109,6 @@ private fun KtAnnotationEntry.fqNameMatches(fqName: String): Boolean { private fun KtAnnotationEntry.getQualifiedName(): String? = analyze(BodyResolveMode.PARTIAL).get(BindingContext.ANNOTATION, this)?.fqName?.asString() +internal fun KtNamedFunction.composePreviewFunctionFqn() = "${getClassName()}.${name}" + diff --git a/idea-plugin/src/main/resources/META-INF/plugin.xml b/idea-plugin/src/main/resources/META-INF/plugin.xml index ca24c7598c..44f5323ab3 100644 --- a/idea-plugin/src/main/resources/META-INF/plugin.xml +++ b/idea-plugin/src/main/resources/META-INF/plugin.xml @@ -24,8 +24,6 @@ - + + +