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
This commit is contained in:
Dmitry.Krasilschikov
2019-12-03 16:04:11 +00:00
committed by intellij-monorepo-bot
parent 2396f2de00
commit bf05d33d14
6 changed files with 58 additions and 77 deletions
@@ -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)) {
@@ -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<Pair<PsiFile?, List<LineFragment>>> {
override fun call(): Pair<PsiFile?, List<LineFragment>> {
private val originalEditor: Editor) : Callable<IntentionPreviewResult?> {
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<PsiFile?, List<LineFragment>>(
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
}
}
}
}
data class IntentionPreviewResult(val psiFile: PsiFile?, val lineFragments: List<LineFragment>)
@@ -22,9 +22,7 @@ internal class IntentionPreviewEditorsPanel(val editors: List<EditorEx>) : 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))
}
@@ -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)
}
@@ -33,11 +33,11 @@ internal class IntentionPreviewModel {
})
}
fun createEditors(project: Project, originalFile: PsiFile, pair: Pair<PsiFile?, List<LineFragment>>?): List<EditorEx> {
if (pair == null) return emptyList()
fun createEditors(project: Project, originalFile: PsiFile, result: IntentionPreviewResult?): List<EditorEx> {
if (result == null) return emptyList()
val psiFileCopy: PsiFile? = pair.first
val lines: List<LineFragment> = pair.second
val psiFileCopy: PsiFile? = result.psiFile
val lines: List<LineFragment> = 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
@@ -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<EditorEx>()
private lateinit var popup: JBPopup
private lateinit var component: IntentionPreviewComponent
private lateinit var updateAdvText: (String) -> Unit
private var editorsToRelease = mutableListOf<EditorEx>()
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<Pair<PsiFile?, List<LineFragment>>?>
fun start() {
component.startLoading()
computation = ReadAction.nonBlocking<Pair<PsiFile?, List<LineFragment>>>(
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
}