diff --git a/platform/lang-impl/src/com/intellij/codeInsight/documentation/QuickDocUtil.java b/platform/lang-impl/src/com/intellij/codeInsight/documentation/QuickDocUtil.java index 72688fea79f4..b33a286f7c6c 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/documentation/QuickDocUtil.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/documentation/QuickDocUtil.java @@ -5,10 +5,15 @@ import com.intellij.codeInsight.hint.HintUtil; import com.intellij.codeInsight.navigation.DocPreviewUtil; import com.intellij.concurrency.SensitiveProgressWrapper; import com.intellij.lang.documentation.DocumentationProvider; +import com.intellij.openapi.Disposable; +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.util.Comparing; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.wm.ToolWindow; import com.intellij.openapi.wm.ToolWindowId; @@ -17,12 +22,18 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.PsiQualifiedNamedElement; import com.intellij.ui.content.Content; import com.intellij.ui.popup.AbstractPopup; +import com.intellij.util.Consumer; +import com.intellij.util.ObjectUtils; +import com.intellij.util.SingleAlarm; +import com.intellij.util.concurrency.AppExecutorUtil; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import static com.intellij.openapi.progress.util.ProgressIndicatorUtils.runInReadActionWithWriteActionPriority; @@ -104,4 +115,63 @@ public class QuickDocUtil { } return null; } + + public static final Object CUT_AT_CMD = ObjectUtils.sentinel("CUT_AT_CMD"); + + public static void updateQuickDocAsync(@NotNull PsiElement element, + @NotNull CharSequence prefix, + @NotNull Consumer> provider) { + Project project = element.getProject(); + StringBuilder sb = new StringBuilder(prefix); + ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue<>(); + Disposable alarmDisposable = Disposer.newDisposable(); + Disposer.register(project, alarmDisposable); + AtomicBoolean stop = new AtomicBoolean(false); + Ref cutAt = Ref.create(null); + SingleAlarm alarm = new SingleAlarm(() -> { + DocumentationComponent component = getActiveDocComponent(project); + if (component == null) { + stop.set(true); + Disposer.dispose(alarmDisposable); + return; + } + Object s = queue.poll(); + while (s != null) { + if (s == CUT_AT_CMD || cutAt.get() == CUT_AT_CMD) { + cutAt.set(s); + s = ""; + } + else if (!cutAt.isNull()) { + int idx = StringUtil.indexOf(sb, cutAt.get().toString()); + if (idx >= 0) sb.setLength(idx); + cutAt.set(null); + } + sb.append(s); + s = queue.poll(); + } + if (stop.get()) { + Disposer.dispose(alarmDisposable); + } + String newText = sb.toString() + "


"; + String prevText = component.getText(); + if (!Comparing.equal(newText, prevText)) { + component.replaceText(newText, element); + } + }, 100, alarmDisposable); + AppExecutorUtil.getAppExecutorService().submit(() -> { + try { + provider.consume(str -> { + ProgressManager.checkCanceled(); + if (stop.get()) throw new ProcessCanceledException(); + queue.add(str); + alarm.cancelAndRequest(); + }); + } + finally { + if (stop.compareAndSet(false, true)) { + alarm.cancelAndRequest(); + } + } + }); + } }