Browse Source
* Implement floating editor toolbar for running preview * Minor: remove accidentally committed debug loggingpull/929/head
Alexey Tsvetkov
3 years ago
committed by
GitHub
12 changed files with 290 additions and 102 deletions
@ -0,0 +1,92 @@ |
|||||||
|
/* |
||||||
|
* 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<PreviewLocation?, Throwable> { |
||||||
|
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("configureDesktopPreview") |
||||||
|
settings.vmOptions = gradleVmOptions |
||||||
|
settings.externalSystemIdString = GradleConstants.SYSTEM_ID.id |
||||||
|
val previewService = project.service<PreviewStateService>() |
||||||
|
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() |
||||||
|
) |
||||||
|
} |
@ -0,0 +1,67 @@ |
|||||||
|
/* |
||||||
|
* 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.application.ReadAction |
||||||
|
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.editor.toolbar.floating.FloatingToolbarComponentImpl |
||||||
|
import com.intellij.openapi.project.Project |
||||||
|
import com.intellij.util.concurrency.AppExecutorUtil |
||||||
|
|
||||||
|
class PreviewFloatingToolbarProvider : AbstractFloatingToolbarProvider(PREVIEW_EDITOR_TOOLBAR_GROUP_ID) { |
||||||
|
override val autoHideable = false |
||||||
|
override val priority: Int = 100 |
||||||
|
|
||||||
|
// todo: disable if not in Compose JVM module |
||||||
|
override fun register(toolbar: FloatingToolbarComponent, parentDisposable: Disposable) { |
||||||
|
try { |
||||||
|
// todo: use provided data context once 2020.3 is no longer supported |
||||||
|
val toolbarImpl = toolbar as? FloatingToolbarComponentImpl ?: return |
||||||
|
val editor = toolbarImpl.getData(CommonDataKeys.EDITOR.name) as? Editor ?: return |
||||||
|
registerComponent(toolbar, editor, parentDisposable) |
||||||
|
} catch (e: Exception) { |
||||||
|
LOG.error(e) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun registerComponent( |
||||||
|
component: FloatingToolbarComponent, |
||||||
|
editor: Editor, |
||||||
|
parentDisposable: Disposable |
||||||
|
) { |
||||||
|
val project = editor.project ?: return |
||||||
|
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) { |
||||||
|
ReadAction.nonBlocking { updateVisibility() } |
||||||
|
.inSmartMode(project) |
||||||
|
.submit(AppExecutorUtil.getAppExecutorService()) |
||||||
|
} |
||||||
|
|
||||||
|
private fun updateVisibility() { |
||||||
|
val parentPreviewFun = parentPreviewAtCaretOrNull(editor) |
||||||
|
if (parentPreviewFun != null) { |
||||||
|
toolbar.scheduleShow() |
||||||
|
} else { |
||||||
|
toolbar.scheduleHide() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
/* |
||||||
|
* 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.util.ExternalSystemApiUtil |
||||||
|
import com.intellij.util.concurrency.annotations.RequiresReadLock |
||||||
|
import org.jetbrains.kotlin.idea.util.projectStructure.module |
||||||
|
import org.jetbrains.kotlin.psi.KtNamedFunction |
||||||
|
|
||||||
|
data class PreviewLocation(val fqName: String, val modulePath: String) |
||||||
|
|
||||||
|
@RequiresReadLock |
||||||
|
internal fun KtNamedFunction.asPreviewFunctionOrNull(): PreviewLocation? { |
||||||
|
if (isValidComposablePreviewFunction()) { |
||||||
|
val fqName = composePreviewFunctionFqn() |
||||||
|
val module = module?.let { ExternalSystemApiUtil.getExternalProjectPath(it) } |
||||||
|
if (module != null) { |
||||||
|
return PreviewLocation(fqName = fqName, modulePath = module) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return null |
||||||
|
|
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
/* |
||||||
|
* 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 |
@ -1,68 +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.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 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 fqName: String, |
|
||||||
private val modulePath: String |
|
||||||
) : AnAction({ "Show non-interactive preview" }, PreviewIcons.RUN_PREVIEW) { |
|
||||||
override fun actionPerformed(e: AnActionEvent) { |
|
||||||
val project = e.project!! |
|
||||||
val previewToolWindow = ToolWindowManager.getInstance(project).getToolWindow("Desktop Preview") |
|
||||||
previewToolWindow?.setAvailable(true) |
|
||||||
|
|
||||||
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 previewService = project.service<PreviewStateService>() |
|
||||||
val gradleCallbackPort = previewService.gradleCallbackPort |
|
||||||
settings.scriptParameters = |
|
||||||
listOf( |
|
||||||
"-Pcompose.desktop.preview.target=$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() |
|
||||||
) |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,42 @@ |
|||||||
|
/* |
||||||
|
* 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.testFramework.LightVirtualFile |
||||||
|
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 |
||||||
|
} |
Loading…
Reference in new issue