Update SpeedSearch in the FileStructure popup

This commit is contained in:
Sergey Malenkov
2018-12-08 15:01:54 +03:00
parent 7e9e5dd691
commit 69c4ddcf00
21 changed files with 379 additions and 63 deletions

View File

@@ -0,0 +1,26 @@
import java.util.EventObject;
class DispatcherClass {
boolean isDispatched(EventObject event) {}
void <caret> dispatchEvent(EventObject event) {}
void flushEvents() {}
class Inner {
void flush() {}
}
interface Dispatcher {
boolean dispatch();
}
class Grand {
class Parent {
class Child {
void dispatch() {}
}
class Inner {
void flush() {}
}
}
class Inner {
void flush() {}
}
}
}

View File

@@ -0,0 +1,10 @@
-ResolveByBetterMethod.java
-DispatcherClass
[dispatchEvent(EventObject): void]
isDispatched(EventObject): boolean
-Dispatcher
dispatch(): boolean
-Grand
-Parent
-Child
dispatch(): void

View File

@@ -0,0 +1,26 @@
import java.util.EventObject;
class DispatcherClass {
boolean isDispatched(EventObject event) {}
void dispatchEvent(EventObject event) {}
void <caret> flushEvents() {}
class Inner {
void flush() {}
}
interface Dispatcher {
boolean dispatch();
}
class Grand {
class Parent {
class Child {
void dispatch() {}
}
class Inner {
void flush() {}
}
}
class Inner {
void flush() {}
}
}
}

View File

@@ -0,0 +1,10 @@
-ResolveByHiddenMethod.java
-DispatcherClass
[dispatchEvent(EventObject): void]
isDispatched(EventObject): boolean
-Dispatcher
dispatch(): boolean
-Grand
-Parent
-Child
dispatch(): void

View File

@@ -0,0 +1,26 @@
import java.util.EventObject;
class <caret> DispatcherClass {
boolean isDispatched(EventObject event) {}
void dispatchEvent(EventObject event) {}
void flushEvents() {}
class Inner {
void flush() {}
}
interface Dispatcher {
boolean dispatch();
}
class Grand {
class Parent {
class Child {
void dispatch() {}
}
class Inner {
void flush() {}
}
}
class Inner {
void flush() {}
}
}
}

View File

@@ -0,0 +1,10 @@
-ResolveByInitialClass.java
-DispatcherClass
[dispatchEvent(EventObject): void]
isDispatched(EventObject): boolean
-Dispatcher
dispatch(): boolean
-Grand
-Parent
-Child
dispatch(): void

View File

@@ -0,0 +1,26 @@
import <caret> java.util.EventObject;
class DispatcherClass {
boolean isDispatched(EventObject event) {}
void dispatchEvent(EventObject event) {}
void flushEvents() {}
class Inner {
void flush() {}
}
interface Dispatcher {
boolean dispatch();
}
class Grand {
class Parent {
class Child {
void dispatch() {}
}
class Inner {
void flush() {}
}
}
class Inner {
void flush() {}
}
}
}

View File

@@ -0,0 +1,10 @@
-ResolveByInitialImport.java
-DispatcherClass
[dispatchEvent(EventObject): void]
isDispatched(EventObject): boolean
-Dispatcher
dispatch(): boolean
-Grand
-Parent
-Child
dispatch(): void

View File

@@ -0,0 +1,26 @@
import java.util.EventObject;
class DispatcherClass {
boolean <caret> isDispatched(EventObject event) {}
void dispatchEvent(EventObject event) {}
void flushEvents() {}
class Inner {
void flush() {}
}
interface Dispatcher {
boolean dispatch();
}
class Grand {
class Parent {
class Child {
void dispatch() {}
}
class Inner {
void flush() {}
}
}
class Inner {
void flush() {}
}
}
}

View File

@@ -0,0 +1,10 @@
-ResolveByWorseMethod.java
-DispatcherClass
[dispatchEvent(EventObject): void]
isDispatched(EventObject): boolean
-Dispatcher
dispatch(): boolean
-Grand
-Parent
-Child
dispatch(): void

View File

@@ -0,0 +1,26 @@
import java.util.EventObject;
class DispatcherClass {
boolean isDispatched(EventObject event) {}
void dispatchEvent(EventObject event) {}
void flushEvents() {}
class Inner {
void flush() {}
}
interface <caret> Dispatcher {
boolean dispatch();
}
class Grand {
class Parent {
class Child {
void dispatch() {}
}
class Inner {
void flush() {}
}
}
class Inner {
void flush() {}
}
}
}

View File

@@ -0,0 +1,10 @@
-ResolveChildrenBeforeSiblings.java
-DispatcherClass
dispatchEvent(EventObject): void
isDispatched(EventObject): boolean
-Dispatcher
[dispatch(): boolean]
-Grand
-Parent
-Child
dispatch(): void

View File

@@ -0,0 +1,26 @@
import java.util.EventObject;
class DispatcherClass {
boolean isDispatched(EventObject event) {}
void dispatchEvent(EventObject event) {}
void flushEvents() {}
class Inner {
void flush() {}
}
interface Dispatcher {
boolean dispatch();
}
class Grand {
class <caret> Parent {
class Child {
void dispatch() {}
}
class Inner {
void flush() {}
}
}
class Inner {
void flush() {}
}
}
}

View File

@@ -0,0 +1,10 @@
-ResolveGrandChildrenBefore.java
-DispatcherClass
dispatchEvent(EventObject): void
isDispatched(EventObject): boolean
-Dispatcher
dispatch(): boolean
-Grand
-Parent
-Child
[dispatch(): void]

View File

@@ -0,0 +1,26 @@
import java.util.EventObject;
class DispatcherClass {
boolean isDispatched(EventObject event) {}
void dispatchEvent(EventObject event) {}
void flushEvents() {}
class Inner {
void flush() {}
}
interface Dispatcher {
boolean dispatch();
}
class Grand {
class Parent {
class Child {
void dispatch() {}
}
class Inner {
void flush() {}
}
}
class <caret> Inner {
void flush() {}
}
}
}

View File

@@ -0,0 +1,10 @@
-ResolveNoSiblingsAndChildren.java
-DispatcherClass
[dispatchEvent(EventObject): void]
isDispatched(EventObject): boolean
-Dispatcher
dispatch(): boolean
-Grand
-Parent
-Child
dispatch(): void

View File

@@ -0,0 +1,26 @@
import java.util.EventObject;
class DispatcherClass {
boolean isDispatched(EventObject event) {}
void dispatchEvent(EventObject event) {}
void flushEvents() {}
class Inner {
void flush() {}
}
interface Dispatcher {
boolean dispatch();
}
class <caret> Grand {
class Parent {
class Child {
void dispatch() {}
}
class Inner {
void flush() {}
}
}
class Inner {
void flush() {}
}
}
}

View File

@@ -0,0 +1,10 @@
-ResolveSiblingsBeforeGrandChildren.java
-DispatcherClass
[dispatchEvent(EventObject): void]
isDispatched(EventObject): boolean
-Dispatcher
dispatch(): boolean
-Grand
-Parent
-Child
dispatch(): void

View File

@@ -1,6 +1,6 @@
-AnonymousAsConstantInInterface.java
-AnonymousAsConstantInInterface
-getAMethod(): void
-[getAMethod(): void]
-$1
getA(): void
getA1(): void
@@ -13,7 +13,7 @@
getA8(): void
-DEFAULT: BuggyInterface = new BuggyInterface() {...}
-$1
[getA1(): void]
getA1(): void
getA2(): void
getA3(): void
getA4(): void

View File

@@ -34,6 +34,17 @@ public class JavaFileStructureFilteringTest extends JavaFileStructureTestCase {
public void testSelectLeafFirst3() {checkTree("clear");}
public void testSelectLeafFirst4() {checkTree("clear");}
public void testResolveByInitialImport() {checkTree("dis");}
public void testResolveByInitialClass() {checkTree("dis");}
public void testResolveByBetterMethod() {checkTree("dis");}
public void testResolveByWorseMethod() {checkTree("dis");}
public void testResolveByHiddenMethod() {checkTree("dis");}
public void testResolveChildrenBeforeSiblings() {checkTree("dis");}
public void testResolveSiblingsBeforeGrandChildren() {checkTree("dis");}
public void testResolveGrandChildrenBefore() {checkTree("dis");}
public void testResolveNoSiblingsAndChildren() {checkTree("dis");}
public void testMatcher() {checkTree("dis");}
public void testMatcher1() {checkTree("ico");}

View File

@@ -80,6 +80,7 @@ import java.awt.datatransfer.Transferable;
import java.awt.event.*;
import java.util.*;
import java.util.List;
import java.util.function.BiPredicate;
/**
* @author Konstantin Bulenkov
@@ -110,7 +111,6 @@ public class FileStructurePopup implements Disposable, TreeActionsOwner {
private final List<JBCheckBox> myAutoClicked = new ArrayList<>();
private String myTestSearchFilter;
private final ActionCallback myTreeHasBuilt = new ActionCallback();
private boolean myInitialNodeIsLeaf;
private final List<Pair<String, JBCheckBox>> myTriggeredCheckboxes = new ArrayList<>();
private final TreeExpander myTreeExpander;
private final CopyPasteDelegator myCopyPasteDelegator;
@@ -174,7 +174,7 @@ public class FileStructurePopup implements Disposable, TreeActionsOwner {
FileStructurePopupFilter filter = new FileStructurePopupFilter();
myFilteringStructure = new FilteringTreeStructure(filter, myTreeStructure, false);
myStructureTreeModel = new StructureTreeModel(myFilteringStructure);
myStructureTreeModel = new StructureTreeModel<>(myFilteringStructure);
myAsyncTreeModel = new AsyncTreeModel(myStructureTreeModel, this);
myAsyncTreeModel.setRootImmediately(myStructureTreeModel.getRootImmediately());
myTree = new MyTree(myAsyncTreeModel);
@@ -389,10 +389,6 @@ public class FileStructurePopup implements Disposable, TreeActionsOwner {
myTree.expandPath(path);
TreeUtil.selectPath(myTree, path);
TreeUtil.ensureSelection(myTree);
Object userObject = TreeUtil.getLastUserObject(path);
if (userObject != null && Comparing.equal(element, StructureViewComponent.unwrapValue(userObject))) {
myInitialNodeIsLeaf = myFilteringStructure.getChildElements(userObject).length == 0;
}
return Promises.resolvedPromise(path);
};
Function<TreePath, Promise<TreePath>> fallback = new Function<TreePath, Promise<TreePath>>() {
@@ -991,9 +987,48 @@ public class FileStructurePopup implements Disposable, TreeActionsOwner {
@Override
public Object findElement(String s) {
List<SpeedSearchObjectWithWeight> elements = SpeedSearchObjectWithWeight.findElement(s, this);
return elements.isEmpty() ? null : findClosestTo(myInitialElement, elements);
SpeedSearchObjectWithWeight best = ContainerUtil.getFirstItem(elements);
if (best == null) return null;
if (myInitialElement instanceof PsiElement) {
PsiElement initial = (PsiElement)myInitialElement;
// find children of the initial element
SpeedSearchObjectWithWeight bestForParent = find(initial, elements, FileStructurePopup::isParent);
if (bestForParent != null) return bestForParent.node;
// find siblings of the initial element
PsiElement parent = initial.getParent();
if (parent != null) {
SpeedSearchObjectWithWeight bestSibling = find(parent, elements, FileStructurePopup::isParent);
if (bestSibling != null) return bestSibling.node;
}
// find grand children of the initial element
SpeedSearchObjectWithWeight bestForAncestor = find(initial, elements, FileStructurePopup::isAncestor);
if (bestForAncestor != null) return bestForAncestor.node;
}
return best.node;
}
}
@Nullable
private static SpeedSearchObjectWithWeight find(@NotNull PsiElement element,
@NotNull List<SpeedSearchObjectWithWeight> objects,
@NotNull BiPredicate<PsiElement, TreePath> predicate) {
return ContainerUtil.find(objects, object -> predicate.test(element, ObjectUtils.tryCast(object.node, TreePath.class)));
}
private static boolean isElement(@NotNull PsiElement element, @Nullable TreePath path) {
return element.equals(StructureViewComponent.unwrapValue(TreeUtil.getLastUserObject(FilteringTreeStructure.FilteringNode.class, path)));
}
private static boolean isParent(@NotNull PsiElement parent, @Nullable TreePath path) {
return path != null && isElement(parent, path.getParentPath());
}
private static boolean isAncestor(@NotNull PsiElement ancestor, @Nullable TreePath path) {
while (path != null) {
if (isElement(ancestor, path)) return true;
path = path.getParentPath();
}
return false;
}
static class MyTree extends DnDAwareTree implements PlaceProvider<String> {
@@ -1014,58 +1049,4 @@ public class FileStructurePopup implements Disposable, TreeActionsOwner {
return ActionPlaces.STRUCTURE_VIEW_POPUP;
}
}
// todo remove ASAP ------------------------------------
@Nullable
private Object findClosestTo(Object path, List<SpeedSearchObjectWithWeight> paths) {
if (path == null || !(myInitialElement instanceof PsiElement)) {
return paths.get(0).node;
}
Collection<PsiElement> parents = getAllParents((PsiElement)myInitialElement);
List<SpeedSearchObjectWithWeight> cur = new ArrayList<>();
int max = -1;
for (SpeedSearchObjectWithWeight p : paths) {
Object component = ((TreePath)p.node).getLastPathComponent();
FilteringTreeStructure.FilteringNode node = TreeUtil.getUserObject(FilteringTreeStructure.FilteringNode.class, component);
if (node != null) {
List<PsiElement> elements = new ArrayList<>();
FilteringTreeStructure.FilteringNode candidate = node;
while (node != null) {
elements.add(getPsi(node));
node = node.getParentNode();
}
final int size = ContainerUtil.intersection(parents, elements).size();
if (size == elements.size() - 1 && size == parents.size() - (myInitialNodeIsLeaf ? 1 : 0) && candidate.children().isEmpty()) {
return p.node;
}
if (size > max) {
max = size;
cur.clear();
cur.add(p);
}
else if (size == max) {
cur.add(p);
}
}
}
Collections.sort(cur, (o1, o2) -> {
final int i = o1.compareWith(o2);
return i != 0 ? i
: ((TreePath)o2.node).getPathCount() - ((TreePath)o1.node).getPathCount();
});
return cur.isEmpty() ? null : cur.get(0).node;
}
@Nullable
private static PsiElement getPsi(FilteringTreeStructure.FilteringNode n) {
return ObjectUtils.tryCast(StructureViewComponent.unwrapValue(n), PsiElement.class);
}
private static Collection<PsiElement> getAllParents(PsiElement element) {
return PsiTreeUtil.collectParents(element, PsiElement.class, true, e -> e instanceof PsiDirectory);
}
}