-
Notifications
You must be signed in to change notification settings - Fork 120
Add quick-filter to dependency tree/table #1737
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Add quick-filter to dependency tree/table #1737
Conversation
d94e714
to
64845c3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for this PR, I really appreciate it and I think that can be very helpful!
Unfortunately there is currently an issue with cyclic-dependencies in TPs.
fFilteredTreeViewer = new FilteredTree(parent, SWT.MULTI | SWT.V_SCROLL | SWT.H_SCROLL, new PatternFilter(), | ||
true, true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately the bundles in a target-platform can have cycles and when such cycle is encountered one gets a StackOverflowException
.
I have tried to create a guard for that situation, but I'm not yet sure this is sufficient for all cases. In general we could also think about adding that to PatternFilter
directly (maybe optionally).
fFilteredTreeViewer = new FilteredTree(parent, SWT.MULTI | SWT.V_SCROLL | SWT.H_SCROLL, new PatternFilter(), | |
true, true); | |
fFilteredTreeViewer = new FilteredTree(parent, SWT.MULTI | SWT.V_SCROLL | SWT.H_SCROLL, new PatternFilter() { | |
private final Map<Object, Boolean> visited = new HashMap<>(); | |
int depth = 0; | |
@Override | |
public boolean isElementVisible(Viewer viewer, Object element) { | |
depth++; | |
Boolean isMatch = visited.get(element); | |
if (isMatch == null) { | |
visited.put(element, false); | |
// Put in surrogate to prevent stack-overflows | |
isMatch = super.isElementVisible(viewer, element); | |
visited.put(element, isMatch); | |
} | |
depth--; | |
if (depth == 0) { | |
visited.clear(); | |
} | |
return isMatch; | |
} | |
}, true, true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you have an example for such a cycle so that I can also test this on my side?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The TP that is active in that case is the following wild mixture (I haven't stripped it down yet, but I think it's also not that important for this issue):
<target includeMode="feature" name="featureBased">
<locations>
<location includeAllPlatforms = "false" includeConfigurePhase = "true" includeMode="planner" includeSource="true" type="InstallableUnit">
<repository location="https://download.eclipse.org/releases/2023-12/"/>
<unit id="org.eclipse.pde.feature.group" />
<unit id="org.eclipse.m2e.pde.feature.feature.group"/>
<unit id="jakarta.annotation-api" version="0.0.0"/>
<unit id="org.eclipse.ecf.filetransfer.ssl.feature.feature.group" version="[1.0.0,2.0.0)"/>
<unit id="org.eclipse.ecf.filetransfer.httpclient5.feature.feature.group" version="[1.0.0,2.0.0)"/>
<unit id="org.eclipse.rcp.feature.group"/>
<unit id="org.eclipse.ecf.core.feature.feature.group" />
<unit id="org.eclipse.ecf.filetransfer.ssl.feature.source.feature.group" version="0.0.0"/>
<unit id="org.eclipse.ecf.core.feature.source.feature.group"/>
<unit id="org.eclipse.acceleo.feature.group" version="3.7.14.202311201319"/>
<unit id="org.eclipse.acceleo.source.feature.group" version="3.7.14.202311201319"/>
<unit id="com.google.guava"/>
<unit id="org.eclipse.rap.jface"/>
<unit id="org.eclipse.sdk.feature.group"/>
<unit id="org.eclipse.m2e.sdk.feature.feature.group"/>
<!--unit id="org.eclipse.emf.rap.sdk.feature.group"/-->
</location>
</locations>
</target>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
64845c3
to
d1c6d0a
Compare
@ptziegler could we complete this soon? |
@HannesWell I can try to have a look at the weekend, but things are really busy right now... |
Sure, no problem. It would be great if we could have this in the upcoming release (which has M3 on Thursday) and RC1 freeze on Monday/Tuesday. |
e23488f
to
b1beffc
Compare
I've updated the PR to also take cycles into consideration. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the update.
ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/project/PluginDependencyTests.java
Outdated
Show resolved
Hide resolved
ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/project/PluginDependencyTests.java
Outdated
Show resolved
Hide resolved
return fFilteredTreeViewer; | ||
} | ||
|
||
private static class DependencyFilteredTree extends FilteredTree { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please explain, why it's now necessary to subclass FilteredTree
with a lot of non-trivial logic, that to some extend seem to be similar to the parent class (but I fail to understand the difference)?
If that's required to make the cycle-prevention work it would be an additional argument for me to move it up the hiearachy, i.e. into
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When you open the dependency view, the tree is automatically expanded to show the direct dependencies. But when you remove the filter, the entire tree is collapsed. This is an inconsistent behavior caused by the following part of the base class:
if (!narrowingDown) {
// collapse all
TreeItem[] is = treeViewer.getTree().getItems();
for (TreeItem item : is) {
if (item.getExpanded()) {
treeViewer.setExpandedState(item.getData(), false);
}
}
}
If you disable the narrowingDown
, the tree isn't collapsed at all. If you hook it to an IJobChangeListener
and expand the tree after the job has finished, you end up with flickering because both the collapse and the expansion are rendered. So the alternative is to use a custom job.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've thought about this problem and I'm also not happy about effectively duplicating the code from the FilteredTree, just to change the way the tree is collapsed.
Once the platform is open again, I'll try to make this mechanism configurable with either "collapse all" or "collapse to auto-level".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once the platform is open again, I'll try to make this mechanism configurable with either "collapse all" or "collapse to auto-level".
Thanks, I was about to suggest the same.
@Override | ||
public Object[] filter(Viewer viewer, Object parent, Object[] elements) { | ||
try { | ||
return super.filter(viewer, parent, elements); | ||
} finally { | ||
visited.clear(); | ||
} | ||
} | ||
|
||
@Override | ||
protected boolean isParentMatch(Viewer viewer, Object element) { | ||
// Cycle detection | ||
if (!visited.add(element)) { | ||
return false; | ||
} | ||
return super.isParentMatch(viewer, element); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comparing this to my proposal I was first concerned that this implementation might lead to false results, because the same parent might be reached through multiple paths, in this case if e.g. the root plugin requires multiple plugins, which require the same plugin, which in turn requires the plugin we searched for.
I tried it with the TP I posted above and the results looked actually good as far as I can tell, but my concerns have not yet fulled vanished.
Can you tell with certainty what is right and what's wrong here? And can you please explain if/why, one approach is better then the other?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
because the same parent might be reached through multiple paths
That doesn't matter. The filter is applied on each element in the tree, so it only matters if the item has at least one child that matches the filter. Lets take the following example:
When the filter is modified, the entire tree is refreshed. This refresh is done in a top-down fashion, so in the example above, the first level is filtered, then the second and then the third.
For each tree item, the pattern filter then goes through its sub-tree, to see if there is any element matching the filter. This is where the cycle check is needed, in order to handle the case where the sub-tree has infinite elements.
Or to rephrase:
Plugin-A
is shown because it containsPlugin-C
in its sub-tree.Plugin-B
is shown because it containsPlugin-C
in its sub-treePlugin-C
is shown because it isPlugin-C
.
Can you tell with certainty what is right and what's wrong here?
Both approaches effectively solve the same problem and I don't think any on those are wrong.
This adds a search field to the tree/table viewer used in the dependency view to allow user to check whether a specific artifact appears in the dependency chain.
b1beffc
to
16533a3
Compare
This adds a search field to the tree/table viewer used in the dependency view to allow user to check whether a specific artifact appears in the dependency chain.