mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
UX-931 intentions preview: initial commit
GitOrigin-RevId: c66e5007f0a12668b7012c1d8072283ab8adbf43
This commit is contained in:
committed by
intellij-monorepo-bot
parent
1f6eff05b9
commit
29196834cc
+37
@@ -7,6 +7,7 @@ import com.intellij.codeInsight.hint.*;
|
||||
import com.intellij.codeInsight.intention.IntentionAction;
|
||||
import com.intellij.codeInsight.intention.IntentionActionDelegate;
|
||||
import com.intellij.codeInsight.intention.impl.config.IntentionManagerSettings;
|
||||
import com.intellij.codeInsight.intention.impl.preview.IntentionPreviewPopupUpdateProcessor;
|
||||
import com.intellij.codeInsight.unwrap.ScopeHighlighter;
|
||||
import com.intellij.codeInspection.SuppressIntentionActionFromFix;
|
||||
import com.intellij.icons.AllIcons;
|
||||
@@ -40,10 +41,12 @@ import com.intellij.ui.PopupMenuListenerAdapter;
|
||||
import com.intellij.ui.awt.RelativePoint;
|
||||
import com.intellij.ui.icons.RowIcon;
|
||||
import com.intellij.ui.popup.WizardPopup;
|
||||
import com.intellij.ui.popup.list.ListPopupImpl;
|
||||
import com.intellij.util.Alarm;
|
||||
import com.intellij.util.ThreeState;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.ui.EmptyIcon;
|
||||
import kotlin.Unit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
@@ -77,6 +80,9 @@ public class IntentionHintComponent implements Disposable, ScrollAwareHint {
|
||||
|
||||
private static final Border INACTIVE_BORDER = BorderFactory.createEmptyBorder(NORMAL_BORDER_SIZE, NORMAL_BORDER_SIZE, NORMAL_BORDER_SIZE, NORMAL_BORDER_SIZE);
|
||||
private static final Border INACTIVE_BORDER_SMALL = BorderFactory.createEmptyBorder(SMALL_BORDER_SIZE, SMALL_BORDER_SIZE, SMALL_BORDER_SIZE, SMALL_BORDER_SIZE);
|
||||
public static final KeyboardShortcut ALT_SPACE_SHORTCUT = KeyboardShortcut.fromString(("alt SPACE"));
|
||||
public static final String ALT_SPACE_SHORTCUT_TEXT = KeymapUtil.getShortcutText(ALT_SPACE_SHORTCUT);
|
||||
private final IntentionPreviewPopupUpdateProcessor myPreviewPopupUpdateProcessor;
|
||||
|
||||
@TestOnly
|
||||
public CachedIntentions getCachedIntentions() {
|
||||
@@ -314,6 +320,7 @@ public class IntentionHintComponent implements Disposable, ScrollAwareHint {
|
||||
ApplicationManager.getApplication().assertIsDispatchThread();
|
||||
myFile = file;
|
||||
myEditor = editor;
|
||||
myPreviewPopupUpdateProcessor = new IntentionPreviewPopupUpdateProcessor(project, myFile, myEditor);
|
||||
myCachedIntentions = cachedIntentions;
|
||||
myPanel.setLayout(new BorderLayout());
|
||||
myPanel.setOpaque(false);
|
||||
@@ -438,6 +445,7 @@ public class IntentionHintComponent implements Disposable, ScrollAwareHint {
|
||||
}
|
||||
}
|
||||
}
|
||||
registerShowPreviewAction();
|
||||
}
|
||||
|
||||
boolean committed = PsiDocumentManager.getInstance(myFile.getProject()).isCommitted(myEditor.getDocument());
|
||||
@@ -464,6 +472,9 @@ public class IntentionHintComponent implements Disposable, ScrollAwareHint {
|
||||
final Object selectedItem = PlatformDataKeys.SELECTED_ITEM.getData((DataProvider)source);
|
||||
if (selectedItem instanceof IntentionActionWithTextCaching) {
|
||||
IntentionAction action = IntentionActionDelegate.unwrap(((IntentionActionWithTextCaching)selectedItem).getAction());
|
||||
if (myPopup instanceof ListPopupImpl) {
|
||||
updatePreviewPopup(action, ((ListPopupImpl)myPopup).getList().getSelectedIndex());
|
||||
}
|
||||
if (action instanceof SuppressIntentionActionFromFix) {
|
||||
if (injectedFile != null && ((SuppressIntentionActionFromFix)action).isShouldBeAppliedToInjectionHost() == ThreeState.NO) {
|
||||
final PsiElement at = injectedFile.findElementAt(injectedEditor.getCaretModel().getOffset());
|
||||
@@ -504,6 +515,32 @@ public class IntentionHintComponent implements Disposable, ScrollAwareHint {
|
||||
Disposer.register(myPopup, ApplicationManager.getApplication()::assertIsDispatchThread);
|
||||
}
|
||||
|
||||
private void updatePreviewPopup(@NotNull IntentionAction action, int index) {
|
||||
myPreviewPopupUpdateProcessor.setup(text -> {
|
||||
myPopup.setAdText(text, SwingConstants.LEFT);
|
||||
return Unit.INSTANCE;
|
||||
}, index);
|
||||
myPreviewPopupUpdateProcessor.updatePopup(action);
|
||||
}
|
||||
|
||||
private void registerShowPreviewAction() {
|
||||
((WizardPopup)myPopup).registerAction("showIntentionPreview", ALT_SPACE_SHORTCUT.getFirstKeyStroke(), new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
myPreviewPopupUpdateProcessor.toggleShow();
|
||||
if (myPopup instanceof ListPopupImpl) {
|
||||
JList list = ((ListPopupImpl)myPopup).getList();
|
||||
int selectedIndex = list.getSelectedIndex();
|
||||
Object selectedValue = list.getSelectedValue();
|
||||
if (selectedValue instanceof IntentionActionWithTextCaching) {
|
||||
updatePreviewPopup(((IntentionActionWithTextCaching)selectedValue).getAction(), selectedIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
myPopup.setAdText(CodeInsightBundle.message("intention.preview.adv.show.text", ALT_SPACE_SHORTCUT_TEXT), SwingConstants.LEFT);
|
||||
}
|
||||
|
||||
void canceled(@NotNull ListPopupStep intentionListStep) {
|
||||
if (myPopup.getListStep() != intentionListStep || myDisposed) {
|
||||
return;
|
||||
|
||||
+1
-1
@@ -216,7 +216,7 @@ public class ShowIntentionActionsHandler implements CodeInsightActionHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private static void invokeIntention(@NotNull IntentionAction action, @Nullable Editor editor, @NotNull PsiFile file) {
|
||||
public 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)) {
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
// Copyright 2000-2019 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.
|
||||
package com.intellij.codeInsight.intention.impl.preview
|
||||
|
||||
import com.intellij.codeInsight.CodeInsightBundle
|
||||
import com.intellij.ide.plugins.MultiPanel
|
||||
import com.intellij.openapi.editor.colors.EditorColorsManager
|
||||
import com.intellij.openapi.editor.ex.EditorEx
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.ui.components.JBLoadingPanel
|
||||
import com.intellij.util.ui.JBUI
|
||||
import java.awt.BorderLayout
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JLabel
|
||||
|
||||
class IntentionPreviewComponent(project: Project) : JBLoadingPanel(BorderLayout(),
|
||||
{ panel -> IntentionPreviewLoadingDecorator(panel, project) }) {
|
||||
private var NO_PREVIEW_LABEL = JLabel(CodeInsightBundle.message("intention.preview.no.available.text")).also { setupLabel(it) }
|
||||
private var LOADING_LABEL = JLabel(CodeInsightBundle.message("intention.preview.initial.loading.preview")).also { setupLabel(it) }
|
||||
|
||||
var editors: List<EditorEx> = emptyList()
|
||||
|
||||
val multiPanel: MultiPanel = object : MultiPanel() {
|
||||
override fun create(key: Int): JComponent {
|
||||
return when (key) {
|
||||
NO_PREVIEW -> NO_PREVIEW_LABEL
|
||||
LOADING_PREVIEW -> LOADING_LABEL
|
||||
else -> {
|
||||
if (editors.isEmpty()) return NO_PREVIEW_LABEL
|
||||
|
||||
IntentionPreviewEditorsPanel(mutableListOf<EditorEx>().apply { addAll<EditorEx>(editors) })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
add(multiPanel)
|
||||
setLoadingText(CodeInsightBundle.message("intention.preview.loading.preview"))
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val NO_PREVIEW = -1
|
||||
const val LOADING_PREVIEW = -2
|
||||
|
||||
private fun setupLabel(label: JLabel) {
|
||||
label.border = JBUI.Borders.empty(3)
|
||||
label.background = EditorColorsManager.getInstance().globalScheme.defaultBackground
|
||||
}
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
// Copyright 2000-2019 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.
|
||||
package com.intellij.codeInsight.intention.impl.preview
|
||||
|
||||
import com.intellij.codeInsight.daemon.impl.ShowIntentionsPass
|
||||
import com.intellij.codeInsight.intention.IntentionAction
|
||||
import com.intellij.codeInsight.intention.impl.CachedIntentions
|
||||
import com.intellij.codeInsight.intention.impl.IntentionActionWithTextCaching
|
||||
import com.intellij.codeInsight.intention.impl.ShowIntentionActionsHandler
|
||||
import com.intellij.diff.comparison.ComparisonManager
|
||||
import com.intellij.diff.comparison.ComparisonPolicy
|
||||
import com.intellij.diff.fragments.LineFragment
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.command.CommandProcessor
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.editor.Document
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.EditorFactory
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||
import com.intellij.openapi.progress.DumbProgressIndicator
|
||||
import com.intellij.openapi.progress.ProcessCanceledException
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Computable
|
||||
import com.intellij.openapi.util.ThrowableComputable
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.PsiFileFactory
|
||||
|
||||
class IntentionPreviewComputable(private val originalFile: PsiFile,
|
||||
private val project: Project,
|
||||
private val originalEditor: Editor,
|
||||
private val action: IntentionAction) : Computable<Pair<PsiFile?, List<LineFragment>>?> {
|
||||
private val LOG = Logger.getInstance(IntentionPreviewComputable::class.java)
|
||||
|
||||
override fun compute(): Pair<PsiFile?, List<LineFragment>>? {
|
||||
ProgressManager.checkCanceled()
|
||||
val psiFileCopy = ApplicationManager.getApplication().runReadAction(Computable<PsiFile> { nonPhysicalPsiCopy(originalFile, project) })
|
||||
ProgressManager.checkCanceled()
|
||||
val documentCopy = ApplicationManager.getApplication().runReadAction(
|
||||
Computable<Document> { FileDocumentManager.getInstance().getDocument(psiFileCopy.viewProvider.virtualFile) })
|
||||
if (documentCopy == null) return null
|
||||
|
||||
CommandProcessor.getInstance().runUndoTransparentAction(Runnable {
|
||||
try {
|
||||
ApplicationManager.getApplication().runReadAction(ThrowableComputable<Unit, RuntimeException> {
|
||||
val editorCopy = EditorFactory.getInstance().createEditor(documentCopy, project)
|
||||
.also { it.caretModel.moveToOffset(originalEditor.caretModel.offset) }
|
||||
|
||||
val actionWithTextCachingCopy = intentionActionWithTextCaching(editorCopy, psiFileCopy)
|
||||
if (actionWithTextCachingCopy == null) return@ThrowableComputable
|
||||
|
||||
try {
|
||||
originalEditor.document.setReadOnly(true)
|
||||
val fileEditorPair = ShowIntentionActionsHandler.chooseFileForAction(psiFileCopy, editorCopy, actionWithTextCachingCopy.action)
|
||||
?: return@ThrowableComputable
|
||||
ProgressManager.checkCanceled()
|
||||
actionWithTextCachingCopy.action.invoke(project, fileEditorPair.second, fileEditorPair.first)
|
||||
ProgressManager.checkCanceled()
|
||||
}
|
||||
finally {
|
||||
originalEditor.document.setReadOnly(false)
|
||||
ApplicationManager.getApplication().invokeLater(Runnable { EditorFactory.getInstance().releaseEditor(editorCopy) })
|
||||
}
|
||||
})
|
||||
}
|
||||
catch (e: Exception) {
|
||||
LOG.warn("There are exceptions on invocation the intention: '${action.text}' on a copy of the file.", e)
|
||||
throw ProcessCanceledException()
|
||||
}
|
||||
})
|
||||
|
||||
return Pair<PsiFile?, List<LineFragment>>(
|
||||
psiFileCopy,
|
||||
PsiDocumentManager.getInstance(project).commitAndRunReadAction(Computable {
|
||||
ComparisonManager.getInstance().compareLines(originalFile.text, psiFileCopy.text, ComparisonPolicy.TRIM_WHITESPACES,
|
||||
DumbProgressIndicator.INSTANCE)
|
||||
}))
|
||||
}
|
||||
|
||||
private fun intentionActionWithTextCaching(editorCopy: Editor,
|
||||
psiFileCopy: PsiFile): IntentionActionWithTextCaching? {
|
||||
val actionsToShow = ShowIntentionsPass.getActionsToShow(editorCopy, psiFileCopy, false)
|
||||
val cachedIntentions = CachedIntentions.createAndUpdateActions(project, psiFileCopy, editorCopy, actionsToShow)
|
||||
return getFixes(cachedIntentions).find { it.text == action.text }
|
||||
}
|
||||
|
||||
private fun nonPhysicalPsiCopy(psiFile: PsiFile, project: Project): PsiFile {
|
||||
ProgressManager.checkCanceled()
|
||||
return PsiFileFactory.getInstance(project).createFileFromText(psiFile.name,
|
||||
psiFile.language,
|
||||
psiFile.text, false, true, false,
|
||||
psiFile.virtualFile)
|
||||
}
|
||||
|
||||
|
||||
fun getFixes(cachedIntentions: CachedIntentions): Sequence<IntentionActionWithTextCaching> =
|
||||
sequenceOf<IntentionActionWithTextCaching>()
|
||||
.plus(cachedIntentions.intentions)
|
||||
.plus(cachedIntentions.inspectionFixes)
|
||||
.plus(cachedIntentions.errorFixes)
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
// Copyright 2000-2019 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.
|
||||
package com.intellij.codeInsight.intention.impl.preview
|
||||
|
||||
import com.intellij.openapi.editor.colors.CodeInsightColors
|
||||
import com.intellij.openapi.editor.colors.EditorColorsScheme
|
||||
import com.intellij.openapi.editor.ex.EditorEx
|
||||
import com.intellij.openapi.ui.VerticalFlowLayout
|
||||
import com.intellij.ui.Gray
|
||||
import com.intellij.ui.JBColor
|
||||
import com.intellij.util.ui.JBUI
|
||||
import javax.swing.JPanel
|
||||
|
||||
class IntentionPreviewEditorsPanel(val editors: List<EditorEx>) : JPanel(VerticalFlowLayout(0, 0)) {
|
||||
init {
|
||||
editors.forEachIndexed { index, editor ->
|
||||
add(editor.component)
|
||||
if (index < editors.size - 1) {
|
||||
add(createSeparatorLine(editor.colorsScheme))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
return JBUI.Panels.simplePanel().withBorder(JBUI.Borders.customLine(color, 1, 0, 0, 0))
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
// Copyright 2000-2019 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.
|
||||
package com.intellij.codeInsight.intention.impl.preview
|
||||
|
||||
import com.intellij.openapi.editor.colors.EditorColorsManager
|
||||
import com.intellij.openapi.editor.colors.EditorFontType
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.LoadingDecorator
|
||||
import com.intellij.ui.ColorUtil
|
||||
import com.intellij.ui.components.panels.NonOpaquePanel
|
||||
import com.intellij.util.ui.AsyncProcessIcon
|
||||
import java.awt.BorderLayout
|
||||
import javax.swing.JLabel
|
||||
import javax.swing.JPanel
|
||||
|
||||
class IntentionPreviewLoadingDecorator(panel: JPanel, project: Project) :
|
||||
LoadingDecorator(panel, project, 500, false, AsyncProcessIcon("IntentionPreviewProcessLoading")) {
|
||||
override fun customizeLoadingLayer(parent: JPanel, text: JLabel, icon: AsyncProcessIcon): NonOpaquePanel {
|
||||
val opaquePanel =
|
||||
NonOpaquePanel(BorderLayout(100, 100)).also {
|
||||
it.isOpaque = true
|
||||
it.background = ColorUtil.withAlpha(EditorColorsManager.getInstance().globalScheme.defaultBackground, 0.6)
|
||||
it.add(icon)
|
||||
it.add(text)
|
||||
}
|
||||
|
||||
parent.layout = BorderLayout()
|
||||
parent.add(opaquePanel)
|
||||
|
||||
val font = EditorColorsManager.getInstance().globalScheme.getFont(EditorFontType.PLAIN)
|
||||
text.font = font.deriveFont(font.style, (font.size + 2).toFloat());
|
||||
|
||||
return opaquePanel
|
||||
}
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
// Copyright 2000-2019 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.
|
||||
package com.intellij.codeInsight.intention.impl.preview
|
||||
|
||||
import com.intellij.diff.comparison.ComparisonManager
|
||||
import com.intellij.diff.comparison.ComparisonPolicy
|
||||
import com.intellij.diff.fragments.LineFragment
|
||||
import com.intellij.openapi.command.WriteCommandAction
|
||||
import com.intellij.openapi.editor.EditorFactory
|
||||
import com.intellij.openapi.editor.ex.EditorEx
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||
import com.intellij.openapi.fileTypes.FileType
|
||||
import com.intellij.openapi.progress.DumbProgressIndicator
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.codeStyle.CodeStyleManager
|
||||
import com.intellij.util.ui.JBUI
|
||||
|
||||
class IntentionPreviewModel {
|
||||
companion object {
|
||||
fun reformatRange(project: Project, psiFileCopy: PsiFile, lineFragment: LineFragment) {
|
||||
val start = lineFragment.startOffset2
|
||||
val end = lineFragment.endOffset2
|
||||
|
||||
if (start >= end) return
|
||||
|
||||
val document = FileDocumentManager.getInstance().getDocument(psiFileCopy.viewProvider.virtualFile)
|
||||
if (document != null) PsiDocumentManager.getInstance(project).commitDocument(document)
|
||||
|
||||
WriteCommandAction.runWriteCommandAction(project, Runnable {
|
||||
CodeStyleManager.getInstance(project).reformatRange(psiFileCopy, start, end, true)
|
||||
})
|
||||
}
|
||||
|
||||
fun createEditors(project: Project, originalFile: PsiFile, pair: Pair<PsiFile?, List<LineFragment>>?): List<EditorEx> {
|
||||
if (pair == null) return emptyList()
|
||||
|
||||
val psiFileCopy: PsiFile? = pair.first
|
||||
val lines: List<LineFragment> = pair.second
|
||||
|
||||
if (psiFileCopy == null) return emptyList()
|
||||
|
||||
lines.forEach { lineFragment -> reformatRange(project, psiFileCopy, lineFragment) }
|
||||
|
||||
return ComparisonManager.getInstance().compareLines(originalFile.text, psiFileCopy.text,
|
||||
ComparisonPolicy.TRIM_WHITESPACES, DumbProgressIndicator.INSTANCE)
|
||||
.mapNotNull {
|
||||
val start = StringUtil.lineColToOffset(psiFileCopy.text, it.startLine2, 0)
|
||||
val end = StringUtil.lineColToOffset(psiFileCopy.text, it.endLine2, 0)
|
||||
|
||||
if (start >= end) return@mapNotNull null
|
||||
|
||||
val text = psiFileCopy.text.substring(start, end).trimStart('\n').trimEnd('\n').trimIndent()
|
||||
if (text.isBlank()) return@mapNotNull null
|
||||
|
||||
return@mapNotNull createEditor(project, originalFile.fileType, text, it.startLine1)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createEditor(project: Project, fileType: FileType, text: String, lineShift: Int): EditorEx {
|
||||
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)) }
|
||||
|
||||
editor.settings.also {
|
||||
it.isLineNumbersShown = true
|
||||
it.isCaretRowShown = false
|
||||
it.isLineMarkerAreaShown = false
|
||||
it.isFoldingOutlineShown = false
|
||||
it.additionalColumnsCount = 0
|
||||
it.additionalLinesCount = 0
|
||||
it.isRightMarginShown = false
|
||||
it.isUseSoftWraps = false
|
||||
it.isAdditionalPageAtBottom = false
|
||||
}
|
||||
|
||||
editor.gutterComponentEx.also {
|
||||
it.setPaintBackground(false)
|
||||
it.setLineNumberConvertor { line -> line + lineShift }
|
||||
}
|
||||
|
||||
return editor
|
||||
}
|
||||
}
|
||||
}
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
// Copyright 2000-2019 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.
|
||||
package com.intellij.codeInsight.intention.impl.preview
|
||||
|
||||
import com.intellij.codeInsight.CodeInsightBundle
|
||||
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.application.ApplicationManager
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.EditorFactory
|
||||
import com.intellij.openapi.editor.ex.EditorEx
|
||||
import com.intellij.openapi.progress.EmptyProgressIndicator
|
||||
import com.intellij.openapi.progress.ProcessCanceledException
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.popup.JBPopup
|
||||
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 java.util.concurrent.Callable
|
||||
import java.util.concurrent.Future
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
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 var progressIndicator: ProgressIndicator = EmptyProgressIndicator()
|
||||
private val alarm = Alarm()
|
||||
|
||||
private lateinit var popup: JBPopup
|
||||
private lateinit var component: IntentionPreviewComponent
|
||||
private lateinit var updateAdvText: (String) -> Unit
|
||||
|
||||
private var editorsToRelease = mutableListOf<EditorEx>()
|
||||
|
||||
override fun updatePopup(lookupItemObject: Any?) {
|
||||
if (!show) return
|
||||
|
||||
alarm.cancelAllRequests()
|
||||
progressIndicator.cancel()
|
||||
|
||||
if (!::popup.isInitialized || popup.isDisposed) {
|
||||
component = IntentionPreviewComponent(project)
|
||||
component.multiPanel.select(LOADING_PREVIEW, true)
|
||||
|
||||
popup = JBPopupFactory.getInstance().createComponentPopupBuilder(component, null)
|
||||
.setCancelCallback { cancel() }
|
||||
.createPopup()
|
||||
|
||||
PopupPositionManager.positionPopupInBestPosition(popup, originalEditor, null)
|
||||
|
||||
updateAdvText.invoke(CodeInsightBundle.message("intention.preview.adv.hide.text"))
|
||||
}
|
||||
|
||||
val value = component.multiPanel.getValue(index, false)
|
||||
if (value != null) {
|
||||
select(index)
|
||||
return
|
||||
}
|
||||
|
||||
val action = lookupItemObject as IntentionAction
|
||||
if (!action.startInWriteAction() || action.getElementToMakeWritable(originalFile) !== originalFile) {
|
||||
select(NO_PREVIEW)
|
||||
return
|
||||
}
|
||||
|
||||
UpdatePopup(project, action, originalFile, originalEditor).start()
|
||||
}
|
||||
|
||||
fun setup(updateAdvConsumer: (String) -> Unit, parentIndex: Int) {
|
||||
index = parentIndex
|
||||
updateAdvText = updateAdvConsumer
|
||||
}
|
||||
|
||||
private fun cancel(): Boolean {
|
||||
editorsToRelease.forEach { EditorFactory.getInstance().releaseEditor(it) }
|
||||
editorsToRelease.clear()
|
||||
component.removeAll()
|
||||
alarm.cancelAllRequests()
|
||||
show = false
|
||||
updateAdvText.invoke(CodeInsightBundle.message("intention.preview.adv.show.text", IntentionHintComponent.ALT_SPACE_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: Future<Pair<PsiFile?, List<LineFragment>>?>
|
||||
|
||||
fun start() {
|
||||
component.startLoading()
|
||||
computation = ApplicationManager.getApplication().executeOnPooledThread(
|
||||
Callable {
|
||||
try {
|
||||
ProgressManager.getInstance().runProcess(IntentionPreviewComputable(originalFile, project, originalEditor, action),
|
||||
progressIndicator)
|
||||
}
|
||||
catch (e: ProcessCanceledException) {
|
||||
Pair<PsiFile?, List<LineFragment>>(null, emptyList())
|
||||
}
|
||||
})
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
private fun select(index: Int, editors: List<EditorEx> = emptyList()) {
|
||||
component.stopLoading()
|
||||
component.editors = editors
|
||||
component.multiPanel.select(index, true)
|
||||
|
||||
popup.pack(true, true)
|
||||
}
|
||||
}
|
||||
@@ -268,6 +268,11 @@ intention.wrap.with.unmodifiable.list=Wrap with unmodifiable list
|
||||
intention.wrap.with.unmodifiable.set=Wrap with unmodifiable set
|
||||
intention.wrap.with.unmodifiable.map=Wrap with unmodifiable map
|
||||
|
||||
intention.preview.adv.show.text=Press {0} to open preview
|
||||
intention.preview.adv.hide.text=Press Escape to hide preview
|
||||
intention.preview.no.available.text=Preview isn't available
|
||||
intention.preview.loading.preview=Loading...
|
||||
intention.preview.initial.loading.preview=Intention Preview
|
||||
lightbulb.tooltip=Click or press {0}
|
||||
dialog.intention.settings.intention.list.title=Intention List
|
||||
dialog.intention.settings.description.panel.title=Description:
|
||||
|
||||
Reference in New Issue
Block a user