Browse Source

Don't create repeated run configurations for preview (#851)

pull/845/head
Alexey Tsvetkov 4 years ago committed by GitHub
parent
commit
45242dc54c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewIcons.kt
  2. 99
      idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewRunConfigurationProducer.kt
  3. 9
      idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewRunLineMarkerContributor.kt
  4. 59
      idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/RunPreviewAction.kt
  5. 2
      idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/locationUtils.kt
  6. 2
      idea-plugin/src/main/resources/META-INF/plugin.xml
  7. 4
      idea-plugin/src/main/resources/icons/compose/runPreview.svg

1
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) private fun load(path: String) = IconLoader.getIcon(path, PreviewIcons::class.java)
val COMPOSE = load("/icons/compose/compose.svg") val COMPOSE = load("/icons/compose/compose.svg")
val RUN_PREVIEW = load("/icons/compose/runPreview.svg")
} }

99
idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewRunConfigurationProducer.kt

@ -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<GradleRunConfiguration>() {
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<PsiElement>
): 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<String> =
listOf(
"-Pcompose.desktop.preview.target=$target",
"-Pcompose.desktop.preview.ide.port=${idePort}"
)
private val ConfigurationContext.port: Int
get() = project.service<PreviewStateService>().gradleCallbackPort
private fun KtNamedFunction.composePreviewFunctionFqn() = "${getClassName()}.${name}"
private fun ConfigurationContext.containingComposePreviewFunction() =
psiLocation?.let { location -> location.getNonStrictParentOfType<KtNamedFunction>()?.takeIf { it.isValidComposePreview() } }

9
idea-plugin/src/main/kotlin/org/jetbrains/compose/desktop/ide/preview/PreviewRunLineMarkerContributor.kt

@ -16,10 +16,11 @@
package org.jetbrains.compose.desktop.ide.preview package org.jetbrains.compose.desktop.ide.preview
import com.intellij.execution.lineMarker.ExecutorAction
import com.intellij.execution.lineMarker.RunLineMarkerContributor import com.intellij.execution.lineMarker.RunLineMarkerContributor
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.impl.source.tree.LeafPsiElement 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.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.psi.KtNamedFunction
@ -37,11 +38,13 @@ class PreviewRunLineMarkerContributor : RunLineMarkerContributor() {
val parent = element.parent val parent = element.parent
return when { return when {
parent is KtNamedFunction && parent.isValidComposePreview() -> { 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!!) } Info(PreviewIcons.COMPOSE, actions) { PreviewMessages.runPreview(parent.name!!) }
} }
else -> null else -> null
} }
} }
} }

59
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<PreviewStateService>().gradleCallbackPort
settings.scriptParameters =
listOf(
"-Pcompose.desktop.preview.target=$fqName",
"-Pcompose.desktop.preview.ide.port=$gradleCallbackPort"
).joinToString(" ")
val future = CompletableFuture<Nothing>()
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()
)
}
}

2
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? = private fun KtAnnotationEntry.getQualifiedName(): String? =
analyze(BodyResolveMode.PARTIAL).get(BindingContext.ANNOTATION, this)?.fqName?.asString() analyze(BodyResolveMode.PARTIAL).get(BindingContext.ANNOTATION, this)?.fqName?.asString()
internal fun KtNamedFunction.composePreviewFunctionFqn() = "${getClassName()}.${name}"

2
idea-plugin/src/main/resources/META-INF/plugin.xml

@ -24,8 +24,6 @@
<runLineMarkerContributor <runLineMarkerContributor
language="kotlin" language="kotlin"
implementationClass="org.jetbrains.compose.desktop.ide.preview.PreviewRunLineMarkerContributor"/> implementationClass="org.jetbrains.compose.desktop.ide.preview.PreviewRunLineMarkerContributor"/>
<runConfigurationProducer
implementation="org.jetbrains.compose.desktop.ide.preview.PreviewRunConfigurationProducer"/>
<deadCode implementation="org.jetbrains.compose.desktop.ide.preview.PreviewEntryPoint" /> <deadCode implementation="org.jetbrains.compose.desktop.ide.preview.PreviewEntryPoint" />
<runLineMarkerContributor <runLineMarkerContributor

4
idea-plugin/src/main/resources/icons/compose/runPreview.svg

@ -0,0 +1,4 @@
<!-- Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<polygon fill="#59A869" fill-rule="evenodd" points="4 2 14 8 4 14"/>
</svg>

After

Width:  |  Height:  |  Size: 309 B

Loading…
Cancel
Save