From bf05d33d14c5732e16716e526d19f270955714fb Mon Sep 17 00:00:00 2001 From: "Dmitry.Krasilschikov" Date: Thu, 28 Nov 2019 15:51:10 +0200 Subject: [PATCH] IDEA-CR-55846 UX-931 intentions preview: corrections by review Use `coalesceBy` and `finishOnUiThread` API for preview updating; better kotlin way coding GitOrigin-RevId: 061d726108500c77300f6bd73c52c634de1d3faa --- .../impl/ShowIntentionActionsHandler.java | 4 +- .../preview/IntentionPreviewComputable.kt | 14 ++-- .../preview/IntentionPreviewEditorsPanel.kt | 4 +- .../IntentionPreviewLoadingDecorator.kt | 14 ++-- .../impl/preview/IntentionPreviewModel.kt | 16 ++-- .../IntentionPreviewPopupUpdateProcessor.kt | 83 +++++++------------ 6 files changed, 58 insertions(+), 77 deletions(-) diff --git a/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/ShowIntentionActionsHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/ShowIntentionActionsHandler.java index 05d1988b2a7e..218326dc7b2d 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/ShowIntentionActionsHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/ShowIntentionActionsHandler.java @@ -42,7 +42,6 @@ import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; import com.intellij.psi.stubs.StubTextInconsistencyException; import com.intellij.util.PairProcessor; import com.intellij.util.ThreeState; -import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -227,8 +226,7 @@ public class ShowIntentionActionsHandler implements CodeInsightActionHandler { } } - @ApiStatus.Internal - public static void invokeIntention(@NotNull IntentionAction action, @Nullable Editor editor, @NotNull PsiFile file) { + private static void invokeIntention(@NotNull IntentionAction action, @Nullable Editor editor, @NotNull PsiFile file) { IntentionsCollector.getInstance().record(file.getProject(), action, file.getLanguage()); PsiElement elementToMakeWritable = action.getElementToMakeWritable(file); if (elementToMakeWritable != null && !FileModificationService.getInstance().preparePsiElementsForWrite(elementToMakeWritable)) { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/preview/IntentionPreviewComputable.kt b/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/preview/IntentionPreviewComputable.kt index d78d4b0ef61d..349b54d85811 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/preview/IntentionPreviewComputable.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/preview/IntentionPreviewComputable.kt @@ -23,16 +23,16 @@ import java.util.concurrent.Callable internal class IntentionPreviewComputable(private val project: Project, private val action: IntentionAction, private val originalFile: PsiFile, - private val originalEditor: Editor) : Callable>> { - override fun call(): Pair> { + private val originalEditor: Editor) : Callable { + override fun call(): IntentionPreviewResult? { val psiFileCopy = nonPhysicalPsiCopy(originalFile, project) ProgressManager.checkCanceled() val editorCopy = IntentionPreviewEditor(psiFileCopy, originalEditor.caretModel.offset) try { - val action = findCopyIntention(project, editorCopy, psiFileCopy, action) ?: throw ProcessCanceledException() + val action = findCopyIntention(project, editorCopy, psiFileCopy, action) ?: return null val fileEditorPair = ShowIntentionActionsHandler.chooseFileForAction(psiFileCopy, editorCopy, action) - ?: throw ProcessCanceledException() + ?: return null val writable = originalEditor.document.isWritable try { @@ -45,7 +45,7 @@ internal class IntentionPreviewComputable(private val project: Project, originalEditor.document.setReadOnly(!writable) } - return Pair>( + return IntentionPreviewResult( psiFileCopy, ComparisonManager.getInstance().compareLines(originalFile.text, editorCopy.document.text, ComparisonPolicy.TRIM_WHITESPACES, DumbProgressIndicator.INSTANCE) @@ -87,4 +87,6 @@ internal class IntentionPreviewComputable(private val project: Project, return getFixes(cachedIntentions).find { it.text == originalAction.text }?.action } } -} \ No newline at end of file +} + +data class IntentionPreviewResult(val psiFile: PsiFile?, val lineFragments: List) \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/preview/IntentionPreviewEditorsPanel.kt b/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/preview/IntentionPreviewEditorsPanel.kt index 38d58e5c38d1..0411f318e3a3 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/preview/IntentionPreviewEditorsPanel.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/preview/IntentionPreviewEditorsPanel.kt @@ -22,9 +22,7 @@ internal class IntentionPreviewEditorsPanel(val editors: List) : JPane private fun createSeparatorLine(colorsScheme: EditorColorsScheme): JPanel { var color = colorsScheme.getColor(CodeInsightColors.METHOD_SEPARATORS_COLOR) - if (color == null) { - color = JBColor.namedColor("Group.separatorColor", JBColor(Gray.xCD, Gray.x51)) - } + color = color?.let { JBColor.namedColor("Group.separatorColor", JBColor(Gray.xCD, Gray.x51)) } return JBUI.Panels.simplePanel().withBorder(JBUI.Borders.customLine(color, 1, 0, 0, 0)) } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/preview/IntentionPreviewLoadingDecorator.kt b/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/preview/IntentionPreviewLoadingDecorator.kt index b23f0de37bd7..55db6320f491 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/preview/IntentionPreviewLoadingDecorator.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/preview/IntentionPreviewLoadingDecorator.kt @@ -9,6 +9,7 @@ import com.intellij.ui.components.panels.NonOpaquePanel import com.intellij.ui.components.panels.OpaquePanel import com.intellij.util.ui.AsyncProcessIcon import java.awt.BorderLayout +import java.awt.Color import java.awt.FlowLayout import javax.swing.JLabel import javax.swing.JPanel @@ -16,17 +17,18 @@ import javax.swing.JPanel internal class IntentionPreviewLoadingDecorator(panel: JPanel, project: Project) : LoadingDecorator(panel, project, 500, false, AsyncProcessIcon("IntentionPreviewProcessLoading")) { override fun customizeLoadingLayer(parent: JPanel, text: JLabel, icon: AsyncProcessIcon): NonOpaquePanel { + val editorBackground = EditorColorsManager.getInstance().globalScheme.defaultBackground val iconNonOpaquePanel = OpaquePanel(FlowLayout(FlowLayout.RIGHT, 2, 2)) - .also { - it.add(icon, BorderLayout.NORTH) - it.background = EditorColorsManager.getInstance().globalScheme.defaultBackground + .apply { + add(icon, BorderLayout.NORTH) + background = editorBackground } - icon.background = ColorUtil.withAlpha(EditorColorsManager.getInstance().globalScheme.defaultBackground, 0.0) + icon.background = editorBackground.withAlpha(0.0) icon.isOpaque = true val opaquePanel = OpaquePanel() - opaquePanel.background = ColorUtil.withAlpha(EditorColorsManager.getInstance().globalScheme.defaultBackground, 0.6) + opaquePanel.background = editorBackground.withAlpha(0.6) val nonOpaquePanel = NonOpaquePanel(BorderLayout()) nonOpaquePanel.add(iconNonOpaquePanel, BorderLayout.EAST) @@ -37,4 +39,6 @@ internal class IntentionPreviewLoadingDecorator(panel: JPanel, project: Project) return nonOpaquePanel } + + fun Color.withAlpha(alpha: Double) = ColorUtil.withAlpha(this, alpha) } \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/preview/IntentionPreviewModel.kt b/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/preview/IntentionPreviewModel.kt index ffb701f831aa..4142ab418382 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/preview/IntentionPreviewModel.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/preview/IntentionPreviewModel.kt @@ -33,11 +33,11 @@ internal class IntentionPreviewModel { }) } - fun createEditors(project: Project, originalFile: PsiFile, pair: Pair>?): List { - if (pair == null) return emptyList() + fun createEditors(project: Project, originalFile: PsiFile, result: IntentionPreviewResult?): List { + if (result == null) return emptyList() - val psiFileCopy: PsiFile? = pair.first - val lines: List = pair.second + val psiFileCopy: PsiFile? = result.psiFile + val lines: List = result.lineFragments if (psiFileCopy == null) return emptyList() @@ -64,7 +64,7 @@ internal class IntentionPreviewModel { val editorFactory = EditorFactory.getInstance() val document = editorFactory.createDocument(text) val editor = (editorFactory.createEditor(document, project, fileType, false) as EditorEx) - .also { it.setBorder(JBUI.Borders.empty(2, 0, 2, 0)) } + .apply { setBorder(JBUI.Borders.empty(2, 0, 2, 0)) } editor.settings.apply { isLineNumbersShown = true @@ -78,9 +78,9 @@ internal class IntentionPreviewModel { isAdditionalPageAtBottom = false } - editor.gutterComponentEx.also { - it.setPaintBackground(false) - it.setLineNumberConvertor { line -> line + lineShift } + editor.gutterComponentEx.apply { + setPaintBackground(false) + setLineNumberConvertor { line -> line + lineShift } } return editor diff --git a/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/preview/IntentionPreviewPopupUpdateProcessor.kt b/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/preview/IntentionPreviewPopupUpdateProcessor.kt index c2da231911bf..52e8695d5009 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/preview/IntentionPreviewPopupUpdateProcessor.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/preview/IntentionPreviewPopupUpdateProcessor.kt @@ -6,8 +6,8 @@ import com.intellij.codeInsight.intention.IntentionAction import com.intellij.codeInsight.intention.impl.IntentionHintComponent import com.intellij.codeInsight.intention.impl.preview.IntentionPreviewComponent.Companion.LOADING_PREVIEW import com.intellij.codeInsight.intention.impl.preview.IntentionPreviewComponent.Companion.NO_PREVIEW -import com.intellij.diff.fragments.LineFragment import com.intellij.openapi.actionSystem.CommonShortcuts.ESCAPE +import com.intellij.openapi.application.ModalityState import com.intellij.openapi.application.ReadAction import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.EditorFactory @@ -19,28 +19,22 @@ import com.intellij.openapi.ui.popup.JBPopupFactory import com.intellij.psi.PsiFile import com.intellij.ui.popup.PopupPositionManager import com.intellij.ui.popup.PopupUpdateProcessor -import com.intellij.util.Alarm import com.intellij.util.concurrency.AppExecutorUtil -import org.jetbrains.concurrency.CancellablePromise -import java.util.concurrent.TimeUnit internal class IntentionPreviewPopupUpdateProcessor(private val project: Project, private val originalFile: PsiFile, private val originalEditor: Editor) : PopupUpdateProcessor(project) { private var index: Int = LOADING_PREVIEW private var show = false - private val alarm = Alarm() + private val editorsToRelease = mutableListOf() private lateinit var popup: JBPopup private lateinit var component: IntentionPreviewComponent - private lateinit var updateAdvText: (String) -> Unit - - private var editorsToRelease = mutableListOf() + private lateinit var updateAdvertiserText: (String) -> Unit override fun updatePopup(intentionAction: Any?) { if (!show) return - alarm.cancelAllRequests() if (!::popup.isInitialized || popup.isDisposed) { component = IntentionPreviewComponent(project) component.multiPanel.select(LOADING_PREVIEW, true) @@ -51,7 +45,7 @@ internal class IntentionPreviewPopupUpdateProcessor(private val project: Project PopupPositionManager.positionPopupInBestPosition(popup, originalEditor, null) - updateAdvText.invoke(CodeInsightBundle.message("intention.preview.adv.hide.text", Companion.ESCAPE_SHORTCUT_TEXT)) + updateAdvertiserText.invoke(CodeInsightBundle.message("intention.preview.adv.hide.text", ESCAPE_SHORTCUT_TEXT)) } val value = component.multiPanel.getValue(index, false) @@ -66,62 +60,47 @@ internal class IntentionPreviewPopupUpdateProcessor(private val project: Project return } - UpdatePopup(project, action, originalFile, originalEditor).start() + component.startLoading() + + ReadAction.nonBlocking( + IntentionPreviewComputable(project, action, originalFile, originalEditor)) + .expireWith(popup) + .coalesceBy(this) + .finishOnUiThread(ModalityState.defaultModalityState()) { renderPreview(it)} + .submit(AppExecutorUtil.getAppExecutorService()) } - fun setup(updateAdvConsumer: (String) -> Unit, parentIndex: Int) { + private fun renderPreview(result: IntentionPreviewResult?) { + try { + val editors = IntentionPreviewModel.createEditors(project, originalFile, result) + if (editors.isEmpty()) { + select(NO_PREVIEW) + return + } + + editorsToRelease.addAll(editors) + select(index, editors) + } + catch (e: Exception) { + select(NO_PREVIEW) + } + } + + fun setup(updateAdvertiser: (String) -> Unit, parentIndex: Int) { index = parentIndex - updateAdvText = updateAdvConsumer + updateAdvertiserText = updateAdvertiser } private fun cancel(): Boolean { editorsToRelease.forEach { EditorFactory.getInstance().releaseEditor(it) } editorsToRelease.clear() component.removeAll() - alarm.cancelAllRequests() show = false - updateAdvText.invoke( + updateAdvertiserText.invoke( CodeInsightBundle.message("intention.preview.adv.show.text", IntentionHintComponent.INTENTION_PREVIEW_SHORTCUT_TEXT)) return true } - inner class UpdatePopup(private val project: Project, - private val action: IntentionAction, - private val originalFile: PsiFile, - private val originalEditor: Editor) : Runnable { - lateinit var computation: CancellablePromise>?> - - fun start() { - component.startLoading() - - computation = ReadAction.nonBlocking>>( - IntentionPreviewComputable(project, action, originalFile, originalEditor)).submit(AppExecutorUtil.getAppExecutorService()) - - alarm.addRequest(this, 100) - } - - override fun run() { - if (!computation.isCancelled && !computation.isDone) { - alarm.addRequest(this, 200) - return - } - - try { - val editors = IntentionPreviewModel.createEditors(project, originalFile, computation.get(3, TimeUnit.SECONDS)) - if (editors.isEmpty()) { - select(NO_PREVIEW) - return - } - - editorsToRelease.addAll(editors) - select(index, editors) - } - catch (e: Exception) { - select(NO_PREVIEW) - } - } - } - fun toggleShow() { show = !show }