[platform] IDEA-261340 Create EdtBgtTreeVisitor to handle complex scenarios

Sometimes there is a visitor delegating to another visitor. When
the call to the delegate that needs BGT is surrounded by code that
needs the EDT, things get messy.

The new DelegatingEdtBgtTreeVisitor class implements the new
EdtBgtTreeVisitor interface and allows to split the visit() call
in three calls. In case the delegate requires the EDT, it's not split,
but everything is invoked in-place. But when a BGT is needed,
then the preVisit and postVisit parts are called from the EDT,
but the visit itself is invoked on a BGT.

The logic is call preVisit first, and if it returns a non-null result,
just use it and skip the rest. Otherwise, call visit() and then
pass its result to postVisit(). It is designed to fit
the TreeUtil.promiseMakeVisible use case, and it's unclear at the
moment whether this approach is flexible enough to cover other
cases as well. This is just the first experimental stage.

(cherry picked from commit eee1a2244deb2a48ad91b7a3da0d28eaa3c17eb2)

IJ-CR-102342

GitOrigin-RevId: 6410acaf4bfc73b696069a9ae687d543dd0c8cdb
This commit is contained in:
Sergei Tachenov
2023-02-01 17:00:02 +02:00
committed by intellij-monorepo-bot
parent 7a901622dd
commit 09dc60d671
5 changed files with 107 additions and 19 deletions

View File

@@ -0,0 +1,27 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ui.tree
import com.intellij.util.ui.EDT
import org.jetbrains.annotations.ApiStatus
import javax.swing.tree.TreePath
@ApiStatus.Internal
abstract class DelegatingEdtBgtTreeVisitor(private val delegate: TreeVisitor) : EdtBgtTreeVisitor {
override fun visit(path: TreePath): TreeVisitor.Action {
if (visitThread() == TreeVisitor.VisitThread.EDT) { // everything is done on the EDT
EDT.assertIsEdt()
val preVisitResult = preVisitEDT(path)
if (preVisitResult != null) {
return preVisitResult
}
return postVisitEDT(path, delegate.visit(path))
}
else { // only the delegating part is done on the EDT, the rest is invoked directly
return delegate.visit(path)
}
}
override fun visitThread(): TreeVisitor.VisitThread = delegate.visitThread()
}

View File

@@ -0,0 +1,25 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ui.tree
import com.intellij.ui.tree.TreeVisitor.Action
import org.jetbrains.annotations.ApiStatus
import javax.swing.tree.TreePath
/**
* A complicated visitor for mixing EDT/BGT logic.
*
* It's intended to be used this way: call [preVisitEDT] on the EDT first,
* and if it returns a non-null value, consider it to be the end result
* of visiting the node. Otherwise, call [visit] on either the EDT or a BGT
* (depending on what [visitThread] returns) and then pass its
* result to [postVisitEDT] on the EDT and consider its return value to be
* the end result.
*/
@ApiStatus.Internal
interface EdtBgtTreeVisitor : TreeVisitor {
fun preVisitEDT(path: TreePath): Action?
fun postVisitEDT(path: TreePath, visitResult: Action): Action
}

View File

@@ -21,6 +21,7 @@ import com.intellij.ui.ScrollingUtil;
import com.intellij.ui.SimpleColoredComponent;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.ui.scale.JBUIScale;
import com.intellij.ui.tree.DelegatingEdtBgtTreeVisitor;
import com.intellij.ui.tree.TreeVisitor;
import com.intellij.ui.treeStructure.Tree;
import com.intellij.util.ObjectUtils;
@@ -1542,9 +1543,29 @@ public final class TreeUtil {
}
private static @NotNull Promise<TreePath> promiseMakeVisible(@NotNull JTree tree, @NotNull TreeVisitor visitor, @NotNull AsyncPromise<?> promise) {
return promiseVisit(tree, path -> {
if (promise.isCancelled()) return TreeVisitor.Action.SKIP_SIBLINGS;
TreeVisitor.Action action = visitor.visit(path);
return promiseVisit(tree, new MakeVisibleVisitor(tree, visitor, promise));
}
private static class MakeVisibleVisitor extends DelegatingEdtBgtTreeVisitor {
private final JTree tree;
private final @NotNull AsyncPromise<?> promise;
private MakeVisibleVisitor(@NotNull JTree tree, @NotNull TreeVisitor delegate, @NotNull AsyncPromise<?> promise) {
super(delegate);
this.tree = tree;
this.promise = promise;
}
@Nullable
@Override
public Action preVisitEDT(@NotNull TreePath path) {
return promise.isCancelled() ? TreeVisitor.Action.SKIP_SIBLINGS : null;
}
@NotNull
@Override
public Action postVisitEDT(@NotNull TreePath path, @NotNull TreeVisitor.Action action) {
if (action == TreeVisitor.Action.CONTINUE || action == TreeVisitor.Action.INTERRUPT) {
// do not expand children if parent path is collapsed
if (!tree.isVisible(path)) {
@@ -1557,7 +1578,7 @@ public final class TreeUtil {
if (action == TreeVisitor.Action.CONTINUE) expandPathWithDebug(tree, path);
}
return action;
});
}
}
/**

View File

@@ -3,6 +3,7 @@ package com.intellij.ui.tree;
import com.intellij.ide.util.treeView.AbstractTreeNode;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -43,6 +44,7 @@ public abstract class AbstractTreeNodeVisitor<T> implements TreeVisitor {
@NotNull
@Override
@RequiresBackgroundThread
public Action visit(@NotNull TreePath path) {
if (LOG.isTraceEnabled()) LOG.debug("process ", path);
T element = getElement();

View File

@@ -18,6 +18,7 @@ internal abstract class BgtTreeWalker<N : Any>(
val promise = AsyncPromise<TreePath>()
private inner class Level(val path: TreePath?, val nodes: ArrayDeque<N>)
private val stack = ArrayDeque<Level>()
@set:RequiresEdt
private var state: State = InitialState
set(value) {
checkValidStateTransition(field, value)
@@ -100,25 +101,19 @@ internal abstract class BgtTreeWalker<N : Any>(
fun enter(node: N, path: TreePath) {
state = this
debug("Visiting node ", node, path)
val edtBgtVisitor = visitor as? EdtBgtTreeVisitor
val preVisitResult = edtBgtVisitor?.preVisitEDT(path)
if (preVisitResult != null) {
processVisitResult(preVisitResult, path, node)
return
}
background.computeLater {
visitor.visit(path)
}.onSuccess { action ->
foreground.invoke {
when (action!!) {
TreeVisitor.Action.INTERRUPT -> {
success.enter(path)
}
TreeVisitor.Action.CONTINUE -> {
requestingChildren.enter(node, path)
}
TreeVisitor.Action.SKIP_CHILDREN -> {
lookingForNextNode.enter()
}
TreeVisitor.Action.SKIP_SIBLINGS -> {
stack.removeLastOrNull()
lookingForNextNode.enter()
}
}
val visitResult = action!!
val postVisitResult = edtBgtVisitor?.postVisitEDT(path, visitResult)
processVisitResult(postVisitResult ?: visitResult, path, node)
}
}.onError { error ->
foreground.invoke {
@@ -127,6 +122,24 @@ internal abstract class BgtTreeWalker<N : Any>(
}
}
private fun processVisitResult(visitResult: TreeVisitor.Action, path: TreePath, node: N) {
when (visitResult) {
TreeVisitor.Action.INTERRUPT -> {
success.enter(path)
}
TreeVisitor.Action.CONTINUE -> {
requestingChildren.enter(node, path)
}
TreeVisitor.Action.SKIP_CHILDREN -> {
lookingForNextNode.enter()
}
TreeVisitor.Action.SKIP_SIBLINGS -> {
stack.removeLastOrNull()
lookingForNextNode.enter()
}
}
}
}
private inner class RequestingChildren : State() {