diff --git a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/UpdateHighlightersUtil.java b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/UpdateHighlightersUtil.java index 3e828323e958..4814771ba790 100644 --- a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/UpdateHighlightersUtil.java +++ b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/UpdateHighlightersUtil.java @@ -31,6 +31,7 @@ import com.intellij.util.concurrency.ThreadingAssertions; import com.intellij.util.containers.ContainerUtil; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -452,13 +453,10 @@ public final class UpdateHighlightersUtil { } /** - * Remove all highlighters with exactly the given range from {@link DocumentMarkupModel}. - * This might be useful in quick fixes and intention actions to provide immediate feedback. - * Note that all highlighters at the given range are removed, not only the ones produced by your inspection, - * but most likely that will look fine: - * they'll be restored when the new highlighting pass is finished. - * This method currently works in O(total highlighter count in file) time. + * Do not use, this method might break highlighting, left for binary compatibility only */ + @Deprecated(forRemoval = true) + @ApiStatus.Internal public static void removeHighlightersWithExactRange(@NotNull Document document, @NotNull Project project, @NotNull Segment range) { if (IntentionPreviewUtils.isIntentionPreviewActive()) return; ThreadingAssertions.assertEventDispatchThread(); @@ -471,26 +469,4 @@ public final class UpdateHighlightersUtil { } } } - - /** - * Remove all highlighters with exactly the given range from {@link DocumentMarkupModel} produced by given inspection. - * This might be useful in quick fixes and intention actions to provide immediate feedback. - * This method currently works in O(total highlighter count in file) time. - */ - public static void removeHighlightersWithExactRange(@NotNull Document document, @NotNull Project project, @NotNull Segment range, @NotNull String inspectionToolId) { - if (IntentionPreviewUtils.isIntentionPreviewActive()) return; - ThreadingAssertions.assertEventDispatchThread(); - MarkupModel model = DocumentMarkupModel.forDocument(document, project, false); - if (model == null) return; - - for (RangeHighlighter highlighter : model.getAllHighlighters()) { - if (TextRange.areSegmentsEqual(range, highlighter)) { - var highlightInfo = HighlightInfo.fromRangeHighlighter(highlighter); - if(highlightInfo == null || !inspectionToolId.equals(highlightInfo.getInspectionToolId())) { - continue; - } - model.removeHighlighter(highlighter); - } - } - } } diff --git a/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/inspection/grammar/quickfix/GrazieAddExceptionQuickFix.kt b/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/inspection/grammar/quickfix/GrazieAddExceptionQuickFix.kt index 37cea01e854f..58f506c9f679 100644 --- a/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/inspection/grammar/quickfix/GrazieAddExceptionQuickFix.kt +++ b/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/inspection/grammar/quickfix/GrazieAddExceptionQuickFix.kt @@ -1,6 +1,5 @@ package com.intellij.grazie.ide.inspection.grammar.quickfix -import com.intellij.codeInsight.daemon.impl.UpdateHighlightersUtil import com.intellij.codeInsight.intention.IntentionAction import com.intellij.codeInspection.IntentionAndQuickFixAction import com.intellij.grazie.GrazieConfig @@ -65,7 +64,7 @@ open class GrazieAddExceptionQuickFix( action.redo() underlineRanges.forEach { underline -> - underline.range?.let { UpdateHighlightersUtil.removeHighlightersWithExactRange(file.viewProvider.document, project, it) } + underline.range?.let { GrazieReplaceTypoQuickFix.removeHighlightersWithExactRange(file.viewProvider.document, project, it) } } UndoManager.getInstance(project).undoableActionPerformed(action) diff --git a/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/inspection/grammar/quickfix/GrazieReplaceTypoQuickFix.kt b/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/inspection/grammar/quickfix/GrazieReplaceTypoQuickFix.kt index b3e34bfff3b1..6a60caa2fca5 100644 --- a/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/inspection/grammar/quickfix/GrazieReplaceTypoQuickFix.kt +++ b/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/inspection/grammar/quickfix/GrazieReplaceTypoQuickFix.kt @@ -2,7 +2,6 @@ package com.intellij.grazie.ide.inspection.grammar.quickfix import ai.grazie.nlp.langs.Language -import com.intellij.codeInsight.daemon.impl.UpdateHighlightersUtil import com.intellij.codeInsight.daemon.impl.actions.IntentionActionWithFixAllOption import com.intellij.codeInsight.intention.CustomizableIntentionAction.RangeToHighlight import com.intellij.codeInsight.intention.FileModifier @@ -11,6 +10,7 @@ import com.intellij.codeInsight.intention.IntentionAction import com.intellij.codeInsight.intention.IntentionActionWithOptions import com.intellij.codeInsight.intention.choice.ChoiceTitleIntentionAction import com.intellij.codeInsight.intention.choice.ChoiceVariantIntentionAction +import com.intellij.codeInsight.intention.preview.IntentionPreviewUtils import com.intellij.codeInspection.LocalQuickFix import com.intellij.codeInspection.util.IntentionFamilyName import com.intellij.codeInspection.util.IntentionName @@ -25,12 +25,15 @@ import com.intellij.grazie.text.TextProblem import com.intellij.openapi.editor.Document import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.colors.EditorColors +import com.intellij.openapi.editor.impl.DocumentMarkupModel import com.intellij.openapi.project.Project import com.intellij.openapi.util.NlsSafe +import com.intellij.openapi.util.Segment import com.intellij.openapi.util.TextRange import com.intellij.psi.PsiFile import com.intellij.psi.SmartPointerManager import com.intellij.psi.SmartPsiFileRange +import com.intellij.util.concurrency.ThreadingAssertions import org.jetbrains.annotations.VisibleForTesting import kotlin.math.min @@ -106,7 +109,7 @@ object GrazieReplaceTypoQuickFix { GrazieFUSCounter.quickFixInvoked(rule, project, "accept.suggestion") val document = file.viewProvider.document ?: return underlineRanges.forEach { underline -> - underline.range?.let { UpdateHighlightersUtil.removeHighlightersWithExactRange(document, project, it) } + underline.range?.let { removeHighlightersWithExactRange(document, project, it) } } applyReplacements(document, replacements) } @@ -231,4 +234,23 @@ object GrazieReplaceTypoQuickFix { } private fun charsMatch(c1: Char, c2: Char) = c1 == c2 || c1 == ' ' && c2 == '\n' + /** + * Remove all highlighters with exactly the given range from [DocumentMarkupModel]. + * This might be useful in quick fixes and intention actions to provide immediate feedback. + * Note that all highlighters at the given range are removed, not only the ones produced by your inspection, + * but most likely that will look fine: + * they'll be restored when the new highlighting pass is finished. + * This method currently works in O(total highlighter count in file) time. + */ + fun removeHighlightersWithExactRange(document: Document, project: Project, range: Segment) { + if (IntentionPreviewUtils.isIntentionPreviewActive()) return + ThreadingAssertions.assertEventDispatchThread() + val model = DocumentMarkupModel.forDocument(document, project, false) ?: return + + for (highlighter in model.allHighlighters) { + if (TextRange.areSegmentsEqual(range, highlighter!!)) { + model.removeHighlighter(highlighter!!) + } + } + } } \ No newline at end of file diff --git a/spellchecker/src/com/intellij/spellchecker/quickfixes/ChangeTo.kt b/spellchecker/src/com/intellij/spellchecker/quickfixes/ChangeTo.kt index 69c7b6b3aa80..a068b2a2002b 100644 --- a/spellchecker/src/com/intellij/spellchecker/quickfixes/ChangeTo.kt +++ b/spellchecker/src/com/intellij/spellchecker/quickfixes/ChangeTo.kt @@ -1,16 +1,18 @@ // Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.spellchecker.quickfixes -import com.intellij.codeInsight.daemon.impl.UpdateHighlightersUtil import com.intellij.codeInsight.intention.FileModifier import com.intellij.codeInsight.intention.HighPriorityAction import com.intellij.codeInsight.intention.choice.ChoiceTitleIntentionAction import com.intellij.codeInsight.intention.choice.ChoiceVariantIntentionAction import com.intellij.codeInsight.intention.choice.DefaultIntentionActionWithChoice +import com.intellij.codeInsight.intention.preview.IntentionPreviewUtils import com.intellij.openapi.editor.Document import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.impl.DocumentMarkupModel import com.intellij.openapi.project.Project import com.intellij.openapi.util.NlsSafe +import com.intellij.openapi.util.Segment import com.intellij.openapi.util.TextRange import com.intellij.openapi.util.registry.Registry import com.intellij.psi.PsiElement @@ -18,6 +20,7 @@ import com.intellij.psi.PsiFile import com.intellij.psi.SmartPointerManager import com.intellij.refactoring.suggested.startOffset import com.intellij.spellchecker.util.SpellCheckerBundle +import com.intellij.util.concurrency.ThreadingAssertions class ChangeTo(typo: String, element: PsiElement, private val range: TextRange) : DefaultIntentionActionWithChoice, LazySuggestions(typo) { private val pointer = SmartPointerManager.getInstance(element.project).createSmartPsiElementPointer(element, element.containingFile) @@ -60,7 +63,7 @@ class ChangeTo(typo: String, element: PsiElement, private val range: TextRange) val document = file.viewProvider.document val myRange = getRange(document) ?: return - UpdateHighlightersUtil.removeHighlightersWithExactRange(document, project, myRange) + removeHighlightersWithExactRange(document, project, myRange) document.replaceString(myRange.startOffset, myRange.endOffset, suggestion) } @@ -88,4 +91,25 @@ class ChangeTo(typo: String, element: PsiElement, private val range: TextRange) return (0 until limit).map { ChangeToVariantAction(it) } } + + /** + * Remove all highlighters with exactly the given range from [DocumentMarkupModel]. + * This might be useful in quick fixes and intention actions to provide immediate feedback. + * Note that all highlighters at the given range are removed, not only the ones produced by your inspection, + * but most likely that will look fine: + * they'll be restored when the new highlighting pass is finished. + * This method currently works in O(total highlighter count in file) time. + */ + fun removeHighlightersWithExactRange(document: Document, project: Project, range: Segment) { + if (IntentionPreviewUtils.isIntentionPreviewActive()) return + ThreadingAssertions.assertEventDispatchThread() + val model = DocumentMarkupModel.forDocument(document, project, false) ?: return + + for (highlighter in model.allHighlighters) { + if (TextRange.areSegmentsEqual(range, highlighter!!)) { + model.removeHighlighter(highlighter!!) + } + } + } + } \ No newline at end of file diff --git a/spellchecker/src/com/intellij/spellchecker/quickfixes/SaveTo.java b/spellchecker/src/com/intellij/spellchecker/quickfixes/SaveTo.java index 3b813466f322..ef447407ba17 100644 --- a/spellchecker/src/com/intellij/spellchecker/quickfixes/SaveTo.java +++ b/spellchecker/src/com/intellij/spellchecker/quickfixes/SaveTo.java @@ -1,15 +1,21 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.spellchecker.quickfixes; -import com.intellij.codeInsight.daemon.impl.UpdateHighlightersUtil; +import com.intellij.codeInsight.daemon.impl.HighlightInfo; import com.intellij.codeInsight.intention.LowPriorityAction; +import com.intellij.codeInsight.intention.preview.IntentionPreviewUtils; import com.intellij.codeInspection.ProblemDescriptor; import com.intellij.codeInspection.ProblemDescriptorUtil; import com.intellij.ide.DataManager; import com.intellij.model.SideEffectGuard; import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.impl.DocumentMarkupModel; +import com.intellij.openapi.editor.markup.MarkupModel; +import com.intellij.openapi.editor.markup.RangeHighlighter; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.util.Segment; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; @@ -19,6 +25,7 @@ import com.intellij.spellchecker.SpellCheckerManager; import com.intellij.spellchecker.inspections.SpellCheckingInspection; import com.intellij.spellchecker.util.SpellCheckerBundle; import com.intellij.ui.components.JBList; +import com.intellij.util.concurrency.ThreadingAssertions; import com.intellij.util.containers.ContainerUtil; import icons.SpellcheckerIcons; import org.jetbrains.annotations.NotNull; @@ -102,9 +109,32 @@ public final class SaveTo implements SpellCheckerQuickFix, LowPriorityAction { SpellCheckerManager.getInstance(project).acceptWordAsCorrect$intellij_spellchecker(word, file.getViewProvider().getVirtualFile(), project, layer); TextRange range = descriptor.getTextRangeInElement().shiftRight(psi.getTextRange().getStartOffset()); - UpdateHighlightersUtil.removeHighlightersWithExactRange(file.getViewProvider().getDocument(), project, range, SpellCheckingInspection.SPELL_CHECKING_INSPECTION_TOOL_NAME); + removeHighlightersWithExactRange(file.getViewProvider().getDocument(), project, range, SpellCheckingInspection.SPELL_CHECKING_INSPECTION_TOOL_NAME); } + /** + * Remove all highlighters with exactly the given range from {@link DocumentMarkupModel} produced by given inspection. + * This might be useful in quick fixes and intention actions to provide immediate feedback. + * This method currently works in O(total highlighter count in file) time. + */ + public static void removeHighlightersWithExactRange(@NotNull Document document, @NotNull Project project, @NotNull Segment range, @NotNull String inspectionToolId) { + if (IntentionPreviewUtils.isIntentionPreviewActive()) return; + ThreadingAssertions.assertEventDispatchThread(); + MarkupModel model = DocumentMarkupModel.forDocument(document, project, false); + if (model == null) return; + + for (RangeHighlighter highlighter : model.getAllHighlighters()) { + if (TextRange.areSegmentsEqual(range, highlighter)) { + var highlightInfo = HighlightInfo.fromRangeHighlighter(highlighter); + if(highlightInfo == null || !inspectionToolId.equals(highlightInfo.getInspectionToolId())) { + continue; + } + model.removeHighlighter(highlighter); + } + } + } + + @Override public Icon getIcon(int flags) { return SpellcheckerIcons.Spellcheck;