mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-02-04 23:39:07 +07:00
[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:
committed by
intellij-monorepo-bot
parent
7a901622dd
commit
09dc60d671
@@ -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()
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user