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