From cea104758e79adda42fbf261cb85c8a2044bc903 Mon Sep 17 00:00:00 2001 From: Konstantin Aleev Date: Fri, 10 May 2019 21:15:53 +0300 Subject: [PATCH] [service-view] extract services to separate tab GitOrigin-RevId: 9d0c0c594cadf8e248bcd87492231d0a29f59247 --- .../services/ServiceViewDescriptor.java | 5 + ...nConfigurationsServiceViewContributor.java | 19 +- .../services/ExtractServiceAction.java | 41 ++ .../execution/services/ServiceModel.java | 253 ++++++++++ .../services/ServiceModelFilter.java | 65 +++ .../execution/services/ServiceSingleView.java | 109 +++++ .../execution/services/ServiceTreeView.java | 45 +- .../execution/services/ServiceView.java | 27 +- .../services/ServiceViewActionProvider.java | 1 + .../services/ServiceViewDragHelper.java | 153 ++++++ .../execution/services/ServiceViewItem.java | 12 - .../services/ServiceViewManagerImpl.java | 379 +++++++++++++-- .../execution/services/ServiceViewModel.java | 440 +++++++++--------- .../services/ServiceViewSingleUi.java | 43 ++ .../execution/services/ServiceViewTree.java | 7 +- .../services/ServiceViewTreeModel.java | 27 +- .../src/idea/LangActions.xml | 6 +- 17 files changed, 1338 insertions(+), 294 deletions(-) create mode 100644 platform/lang-impl/src/com/intellij/execution/services/ExtractServiceAction.java create mode 100644 platform/lang-impl/src/com/intellij/execution/services/ServiceModel.java create mode 100644 platform/lang-impl/src/com/intellij/execution/services/ServiceModelFilter.java create mode 100644 platform/lang-impl/src/com/intellij/execution/services/ServiceSingleView.java create mode 100644 platform/lang-impl/src/com/intellij/execution/services/ServiceViewDragHelper.java delete mode 100644 platform/lang-impl/src/com/intellij/execution/services/ServiceViewItem.java create mode 100644 platform/lang-impl/src/com/intellij/execution/services/ServiceViewSingleUi.java diff --git a/platform/lang-api/src/com/intellij/execution/services/ServiceViewDescriptor.java b/platform/lang-api/src/com/intellij/execution/services/ServiceViewDescriptor.java index 3a4d853cab7f..ce464e3b17a2 100644 --- a/platform/lang-api/src/com/intellij/execution/services/ServiceViewDescriptor.java +++ b/platform/lang-api/src/com/intellij/execution/services/ServiceViewDescriptor.java @@ -21,6 +21,11 @@ public interface ServiceViewDescriptor { return null; } + @NotNull + default ItemPresentation getContentPresentation() { + return getPresentation(); + } + @Nullable default ActionGroup getToolbarActions() { return null; diff --git a/platform/lang-impl/src/com/intellij/execution/dashboard/RunConfigurationsServiceViewContributor.java b/platform/lang-impl/src/com/intellij/execution/dashboard/RunConfigurationsServiceViewContributor.java index 7a1dfa69e95e..93594ca74c48 100644 --- a/platform/lang-impl/src/com/intellij/execution/dashboard/RunConfigurationsServiceViewContributor.java +++ b/platform/lang-impl/src/com/intellij/execution/dashboard/RunConfigurationsServiceViewContributor.java @@ -3,6 +3,7 @@ package com.intellij.execution.dashboard; import com.intellij.execution.ExecutionBundle; import com.intellij.execution.actions.StopAction; +import com.intellij.execution.configurations.RunConfiguration; import com.intellij.execution.dashboard.tree.ConfigurationTypeDashboardGroupingRule; import com.intellij.execution.dashboard.tree.RunConfigurationNode; import com.intellij.execution.runners.FakeRerunAction; @@ -148,7 +149,23 @@ public class RunConfigurationsServiceViewContributor @Override public JComponent getContentComponent() { Content content = node.getContent(); - return content == null ? createEmptyContent() : content.getManager().getComponent(); + if (content == null) return createEmptyContent(); + + ContentManager contentManager = content.getManager(); + return contentManager == null ? null : contentManager.getComponent(); + } + + @NotNull + @Override + public ItemPresentation getContentPresentation() { + Content content = node.getContent(); + if (content != null) { + return new PresentationData(content.getDisplayName(), null, content.getIcon(), null); + } + else { + RunConfiguration configuration = node.getConfigurationSettings().getConfiguration(); + return new PresentationData(configuration.getName(), null, configuration.getIcon(), null); + } } @Override diff --git a/platform/lang-impl/src/com/intellij/execution/services/ExtractServiceAction.java b/platform/lang-impl/src/com/intellij/execution/services/ExtractServiceAction.java new file mode 100644 index 000000000000..67ccac186e31 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/execution/services/ExtractServiceAction.java @@ -0,0 +1,41 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.execution.services; + +import com.intellij.execution.services.ServiceModel.ServiceViewItem; +import com.intellij.execution.services.ServiceViewDragHelper.ServiceViewDragBean; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.project.DumbAwareAction; +import com.intellij.openapi.project.Project; +import org.jetbrains.annotations.NotNull; + +import java.awt.*; +import java.util.Collections; +import java.util.List; + +public class ExtractServiceAction extends DumbAwareAction { + @Override + public void update(@NotNull AnActionEvent e) { + List items = getSelectedItems(e); + e.getPresentation().setEnabled(!items.isEmpty()); + } + + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + Project project = e.getProject(); + if (project == null) return; + + List items = getSelectedItems(e); + if (items.isEmpty()) return; + + ((ServiceViewManagerImpl)ServiceViewManager.getInstance(e.getProject())).extract(new ServiceViewDragBean(items)); + } + + private static List getSelectedItems(@NotNull AnActionEvent e) { + Component contextComponent = e.getData(PlatformDataKeys.CONTEXT_COMPONENT); + while (contextComponent != null && !(contextComponent instanceof ServiceView)) { + contextComponent = contextComponent.getParent(); + } + return contextComponent == null ? Collections.emptyList() : ((ServiceView)contextComponent).getSelectedItems(); + } +} diff --git a/platform/lang-impl/src/com/intellij/execution/services/ServiceModel.java b/platform/lang-impl/src/com/intellij/execution/services/ServiceModel.java new file mode 100644 index 000000000000..72f6cf00ff36 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/execution/services/ServiceModel.java @@ -0,0 +1,253 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.execution.services; + +import com.intellij.ide.util.treeView.NodeDescriptor; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.openapi.project.Project; +import com.intellij.util.concurrency.Invoker; +import com.intellij.util.concurrency.InvokerSupplier; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.concurrency.CancellablePromise; + +import java.util.*; + +class ServiceModel implements Disposable, InvokerSupplier { + static final ExtensionPointName EP_NAME = ExtensionPointName.create("com.intellij.serviceViewContributor"); + + private final Project myProject; + private final Invoker myInvoker = new Invoker.BackgroundThread(this); + private final List myRoots = new ArrayList<>(); + private boolean myRootInitialized; + + ServiceModel(@NotNull Project project) { + myProject = project; + } + + @Override + public void dispose() { + } + + @NotNull + @Override + public Invoker getInvoker() { + return myInvoker; + } + + @NotNull + List getRoots() { + if (!myRootInitialized) { + myRootInitialized = true; + myRoots.clear(); + myRoots.addAll(doGetRoots()); + } + return myRoots; + } + + private List doGetRoots() { + List result = new ArrayList<>(); + for (ServiceViewContributor contributor : EP_NAME.getExtensions()) { + result.addAll(getContributorChildren(myProject, null, contributor)); + } + return result; + } + + @NotNull + CancellablePromise refresh(@NotNull ServiceEventListener.ServiceEvent e) { + return getInvoker().runOrInvokeLater(() -> reset(e.contributorClass)); + } + + private void reset(Class contributorClass) { + int startIndex = -1; + + if (myRoots.isEmpty()) { + startIndex = 0; + } + else { + Map indexes = new HashMap<>(); + List toRemove = new ArrayList<>(); + ServiceViewContributor previous = null; + for (int i = 0; i < myRoots.size(); i++) { + ServiceViewItem child = myRoots.get(i); + if (contributorClass.isInstance(child.getContributor())) { + toRemove.add(child); + if (startIndex < 0) { + startIndex = i; + } + } + else if (previous != child.getContributor()) { + previous = child.getContributor(); + indexes.put(previous, i); + } + } + if (startIndex < 0) { + ServiceViewContributor[] contributors = EP_NAME.getExtensions(); + for (int i = contributors.length - 1; i >= 0; i--) { + if (!contributorClass.isInstance(contributors[i])) { + startIndex = indexes.getOrDefault(contributors[i], Integer.valueOf(-1)); + if (startIndex == 0) { + break; + } + } + else { + break; + } + } + if (startIndex < 0) { + startIndex = myRoots.size() - toRemove.size(); + } + } + myRoots.removeAll(toRemove); + } + + List newChildren = null; + for (ServiceViewContributor contributor : EP_NAME.getExtensions()) { + if (contributorClass.isInstance(contributor)) { + newChildren = getContributorChildren(myProject, null, contributor); + break; + } + } + if (newChildren != null) { + myRoots.addAll(startIndex, newChildren); + } + } + + private static List getContributorChildren(Project project, + ServiceViewItem parent, + ServiceViewContributor contributor) { + Set children = new LinkedHashSet<>(); + Map groupNodes = new HashMap<>(); + for (T service : contributor.getServices(project)) { + Object value = service instanceof ServiceViewProvidingContributor ? ((ServiceViewProvidingContributor)service).asService() : service; + + if (contributor instanceof ServiceViewGroupingContributor) { + ServiceViewGroupingContributor groupingContributor = (ServiceViewGroupingContributor)contributor; + Object group = groupingContributor.groupBy(service); + if (group != null) { + ServiceGroupNode groupNode = groupNodes.get(group); + if (groupNode == null) { + groupNode = new ServiceGroupNode(group, parent, contributor, groupingContributor.getGroupDescriptor(group)); + groupNodes.put(group, groupNode); + } + ServiceViewItem + serviceNode = new ServiceNode(value, groupNode, contributor, contributor.getServiceDescriptor(service), project, + service instanceof ServiceViewContributor ? (ServiceViewContributor)service : null); + groupNode.getChildren().add(serviceNode); + children.add(groupNode); + continue; + } + } + + ServiceViewItem + serviceNode = new ServiceNode(value, parent, contributor, contributor.getServiceDescriptor(service), project, + service instanceof ServiceViewContributor ? (ServiceViewContributor)service : null); + children.add(serviceNode); + } + return new ArrayList<>(children); + } + + abstract static class ServiceViewItem { + private final Object myValue; + private final ServiceViewItem myParent; + private final ServiceViewContributor myContributor; + private final ServiceViewDescriptor myViewDescriptor; + private List myChildren; + private boolean myPresentationUpdated; + + protected ServiceViewItem(@NotNull Object value, @Nullable ServiceViewItem parent, @NotNull ServiceViewContributor contributor, + @NotNull ServiceViewDescriptor viewDescriptor) { + myValue = value; + myParent = parent; + myContributor = contributor; + myViewDescriptor = viewDescriptor; + } + + @NotNull + Object getValue() { + return myValue; + } + + @NotNull + ServiceViewContributor getContributor() { + return myContributor; + } + + @NotNull + ServiceViewContributor getRootContributor() { + return myParent == null ? myContributor : myParent.getRootContributor(); + } + + @NotNull + ServiceViewDescriptor getViewDescriptor() { + if (!myPresentationUpdated) { + myPresentationUpdated = true; + if (myValue instanceof NodeDescriptor) { + ((NodeDescriptor)myValue).update(); + } + } + return myViewDescriptor; + } + + @Nullable + ServiceViewItem getParent() { + return myParent; + } + + @NotNull + List getChildren() { + if (myChildren == null) { + myChildren = doGetChildren(); + } + return myChildren; + } + + @NotNull + abstract List doGetChildren(); + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ServiceViewItem node = (ServiceViewItem)o; + return myValue.equals(node.myValue); + } + + @Override + public int hashCode() { + return myValue.hashCode(); + } + } + + static class ServiceNode extends ServiceViewItem { + private final Project myProject; + private final ServiceViewContributor myProvidingContributor; + + ServiceNode(@NotNull Object service, @Nullable ServiceViewItem parent, @NotNull ServiceViewContributor contributor, + @NotNull ServiceViewDescriptor viewDescriptor, + @NotNull Project project, @Nullable ServiceViewContributor providingContributor) { + super(service, parent, contributor, viewDescriptor); + myProject = project; + myProvidingContributor = providingContributor; + } + + @NotNull + @Override + List doGetChildren() { + return myProvidingContributor == null ? Collections.emptyList() : getContributorChildren(myProject, this, myProvidingContributor); + } + } + + static class ServiceGroupNode extends ServiceViewItem { + ServiceGroupNode(@NotNull Object group, @Nullable ServiceViewItem parent, @NotNull ServiceViewContributor contributor, + @NotNull ServiceViewDescriptor viewDescriptor) { + super(group, parent, contributor, viewDescriptor); + } + + @NotNull + @Override + List doGetChildren() { + return new ArrayList<>(); + } + } +} diff --git a/platform/lang-impl/src/com/intellij/execution/services/ServiceModelFilter.java b/platform/lang-impl/src/com/intellij/execution/services/ServiceModelFilter.java new file mode 100644 index 000000000000..09f21ffe12f5 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/execution/services/ServiceModelFilter.java @@ -0,0 +1,65 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.execution.services; + +import com.intellij.execution.services.ServiceModel.ServiceViewItem; +import com.intellij.openapi.util.Condition; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +class ServiceModelFilter { + private final List myFilters = new CopyOnWriteArrayList<>(); + + void addFilter(@NotNull ServiceViewFilter filter) { + myFilters.add(filter); + } + + void removeFilter(@NotNull ServiceViewFilter filter) { + ServiceViewFilter parent = filter.getParent(); + myFilters.remove(filter); + for (ServiceViewFilter viewFilter : myFilters) { + if (viewFilter.getParent() == filter) { + viewFilter.setParent(parent); + } + } + } + + @NotNull + List filter(@NotNull List items, @Nullable ServiceViewFilter targetFilter) { + if (items.isEmpty()) return items; + + List filters = targetFilter == null ? myFilters : excludeTargetAndParents(targetFilter); + return ContainerUtil.filter(items, item -> filters.stream().noneMatch(filter -> filter.value(item))); + } + + private List excludeTargetAndParents(@NotNull ServiceViewFilter targetFilter) { + List filters = new ArrayList<>(myFilters); + do { + filters.remove(targetFilter); + targetFilter = targetFilter.getParent(); + } + while (targetFilter != null); + return filters; + } + + abstract static class ServiceViewFilter implements Condition { + private ServiceViewFilter myParent; + + protected ServiceViewFilter(@Nullable ServiceViewFilter parent) { + myParent = parent; + } + + @Nullable + ServiceViewFilter getParent() { + return myParent; + } + + private void setParent(@Nullable ServiceViewFilter parent) { + myParent = parent; + } + } +} diff --git a/platform/lang-impl/src/com/intellij/execution/services/ServiceSingleView.java b/platform/lang-impl/src/com/intellij/execution/services/ServiceSingleView.java new file mode 100644 index 000000000000..6dd6b2f172f8 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/execution/services/ServiceSingleView.java @@ -0,0 +1,109 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.execution.services; + +import com.intellij.execution.services.ServiceModel.ServiceViewItem; +import com.intellij.execution.services.ServiceViewModel.ServiceViewModelListener; +import com.intellij.openapi.project.Project; +import com.intellij.ui.AppUIUtil; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.concurrency.Promise; +import org.jetbrains.concurrency.Promises; + +import java.awt.*; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +class ServiceSingleView extends ServiceView { + private final AtomicReference myRef = new AtomicReference<>(); + private boolean mySelected; + private final ServiceViewModelListener myListener; + + ServiceSingleView(@NotNull Project project, @NotNull ServiceViewModel model, @NotNull ServiceViewUi ui) { + super(new BorderLayout(), project, model, ui); + ui.setServiceToolbar(ServiceViewActionProvider.getInstance()); + add(ui.getComponent(), BorderLayout.CENTER); + myListener = new ServiceViewModelListener() { + @Override + public void rootsChanged() { + updateItem(); + } + }; + model.addModelListener(myListener); + model.getInvoker().invokeLater(this::updateItem); + } + + @NotNull + @Override + Promise select(@NotNull Object service, + @NotNull Class contributorClass) { + ServiceViewItem item = myRef.get(); + if (item == null || !item.getValue().equals(service)) { + return Promises.rejectedPromise("Service not found"); + } + + showContent(); + return Promises.resolvedPromise(); + } + + @Override + void saveState(@NotNull ServiceViewState state) { + } + + @Override + void onViewSelected() { + showContent(); + } + + @Override + void onViewUnselected() { + mySelected = false; + ServiceViewItem item = myRef.get(); + if (item != null) { + item.getViewDescriptor().onNodeUnselected(); + } + } + + @NotNull + @Override + List getSelectedItems() { + ServiceViewItem item = myRef.get(); + return item == null ? Collections.emptyList() : Collections.singletonList(item); + } + + @Override + public void dispose() { + getModel().removeModelListener(myListener); + } + + private void updateItem() { + ServiceViewItem oldValue = myRef.get(); + ServiceViewItem newValue = ContainerUtil.getOnlyItem(getModel().getRoots()); + myRef.set(newValue); + AppUIUtil.invokeOnEdt(() -> { + if (mySelected) { + if (newValue != null) { + ServiceViewDescriptor descriptor = newValue.getViewDescriptor(); + if (oldValue == null) { + descriptor.onNodeSelected(); + } + myUi.setDetailsComponent(descriptor.getContentComponent()); + } + } + }, myProject.getDisposed()); + } + + private void showContent() { + if (mySelected) return; + + mySelected = true; + ServiceViewItem item = myRef.get(); + if (item != null) { + ServiceViewDescriptor descriptor = item.getViewDescriptor(); + descriptor.onNodeSelected(); + + myUi.setDetailsComponent(descriptor.getContentComponent()); + } + } +} diff --git a/platform/lang-impl/src/com/intellij/execution/services/ServiceTreeView.java b/platform/lang-impl/src/com/intellij/execution/services/ServiceTreeView.java index 7d5a02eeb0a4..0b19a8479c07 100644 --- a/platform/lang-impl/src/com/intellij/execution/services/ServiceTreeView.java +++ b/platform/lang-impl/src/com/intellij/execution/services/ServiceTreeView.java @@ -1,6 +1,8 @@ // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.execution.services; +import com.intellij.execution.services.ServiceModel.ServiceViewItem; +import com.intellij.ide.dnd.DnDManager; import com.intellij.ide.util.treeView.TreeState; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Comparing; @@ -18,25 +20,24 @@ import javax.swing.tree.TreePath; import java.awt.*; import java.util.List; import java.util.Queue; +import java.util.*; class ServiceTreeView extends ServiceView { - private final Project myProject; private final ServiceViewTree myTree; private final ServiceViewTreeModel myTreeModel; - private final ServiceViewUi myUi; private ServiceViewItem myLastSelection; + private boolean mySelected; ServiceTreeView(@NotNull Project project, @NotNull ServiceViewModel model, @NotNull ServiceViewUi ui, @NotNull ServiceViewState state) { - super(new BorderLayout()); - myProject = project; - myUi = ui; + super(new BorderLayout(), project, model, ui); myTreeModel = new ServiceViewTreeModel(model); myTree = new ServiceViewTree(myTreeModel, this); ServiceViewActionProvider actionProvider = ServiceViewActionProvider.getInstance(); ui.setServiceToolbar(actionProvider); ui.setMasterPanel(myTree, actionProvider); + DnDManager.getInstance().registerSource(ServiceViewDragHelper.createSource(this), myTree); add(myUi.getComponent(), BorderLayout.CENTER); myTree.addTreeSelectionListener(e -> onSelectionChanged()); @@ -53,7 +54,19 @@ class ServiceTreeView extends ServiceView { @NotNull @Override List getSelectedItems() { - return ContainerUtil.mapNotNull(TreeUtil.collectSelectedUserObjects(myTree), o -> ObjectUtils.tryCast(o, ServiceViewItem.class)); + int[] rows = myTree.getSelectionRows(); + if (rows == null || rows.length == 0) return Collections.emptyList(); + + List result = new ArrayList<>(); + Arrays.sort(rows); + for (int row : rows) { + TreePath path = myTree.getPathForRow(row); + ServiceViewItem item = path == null ? null : ObjectUtils.tryCast(path.getLastPathComponent(), ServiceViewItem.class); + if (item != null) { + result.add(item); + } + } + return result; } @Override @@ -70,13 +83,31 @@ class ServiceTreeView extends ServiceView { return Promises.resolvedPromise(); } + @Override + void onViewSelected() { + mySelected = true; + if (myLastSelection != null) { + ServiceViewDescriptor descriptor = myLastSelection.getViewDescriptor(); + descriptor.onNodeSelected(); + myUi.setDetailsComponent(descriptor.getContentComponent()); + } + } + + @Override + void onViewUnselected() { + mySelected = false; + if (myLastSelection != null) { + myLastSelection.getViewDescriptor().onNodeUnselected(); + } + } + private void onSelectionChanged() { List selected = getSelectedItems(); ServiceViewItem newSelection = ContainerUtil.getOnlyItem(selected); if (Comparing.equal(newSelection, myLastSelection)) return; ServiceViewDescriptor oldDescriptor = myLastSelection == null ? null : myLastSelection.getViewDescriptor(); - if (oldDescriptor != null) { + if (oldDescriptor != null && mySelected) { oldDescriptor.onNodeUnselected(); } diff --git a/platform/lang-impl/src/com/intellij/execution/services/ServiceView.java b/platform/lang-impl/src/com/intellij/execution/services/ServiceView.java index 3efa5b25492d..24f1ac6645bf 100644 --- a/platform/lang-impl/src/com/intellij/execution/services/ServiceView.java +++ b/platform/lang-impl/src/com/intellij/execution/services/ServiceView.java @@ -1,6 +1,7 @@ // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.execution.services; +import com.intellij.execution.services.ServiceModel.ServiceViewItem; import com.intellij.ide.DataManager; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.DataProvider; @@ -16,14 +17,25 @@ import java.awt.*; import java.util.List; abstract class ServiceView extends JPanel implements Disposable { - ServiceView(LayoutManager layout) { + protected final Project myProject; + private final ServiceViewModel myModel; + protected final ServiceViewUi myUi; + + ServiceView(LayoutManager layout, @NotNull Project project, @NotNull ServiceViewModel model, @NotNull ServiceViewUi ui) { super(layout); + myProject = project; + myModel = model; + myUi = ui; } @Override public void dispose() { } + ServiceViewModel getModel() { + return myModel; + } + abstract void saveState(@NotNull ServiceViewState state); @NotNull @@ -31,13 +43,24 @@ abstract class ServiceView extends JPanel implements Disposable { abstract Promise select(@NotNull Object service, @NotNull Class contributorClass); + abstract void onViewSelected(); + + abstract void onViewUnselected(); + static ServiceView createTreeView(@NotNull Project project, @NotNull ServiceViewModel model, @NotNull ServiceViewState state) { - ServiceViewTreeUi ui = new ServiceViewTreeUi(state); + ServiceViewUi ui = new ServiceViewTreeUi(state); ServiceView serviceView = new ServiceTreeView(project, model, ui, state); setDataProvider(serviceView); return serviceView; } + static ServiceView createSingleView(@NotNull Project project, @NotNull ServiceViewModel model) { + ServiceViewUi ui = new ServiceViewSingleUi(); + ServiceView serviceView = new ServiceSingleView(project, model, ui); + setDataProvider(serviceView); + return serviceView; + } + private static void setDataProvider(ServiceView serviceView) { serviceView.putClientProperty(DataManager.CLIENT_PROPERTY_DATA_PROVIDER, (DataProvider)dataId -> { if (PlatformDataKeys.HELP_ID.is(dataId)) { diff --git a/platform/lang-impl/src/com/intellij/execution/services/ServiceViewActionProvider.java b/platform/lang-impl/src/com/intellij/execution/services/ServiceViewActionProvider.java index b8aaceada6f7..4b368aca718f 100644 --- a/platform/lang-impl/src/com/intellij/execution/services/ServiceViewActionProvider.java +++ b/platform/lang-impl/src/com/intellij/execution/services/ServiceViewActionProvider.java @@ -1,6 +1,7 @@ // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.execution.services; +import com.intellij.execution.services.ServiceModel.ServiceViewItem; import com.intellij.ide.CommonActionsManager; import com.intellij.ide.DefaultTreeExpander; import com.intellij.ide.TreeExpander; diff --git a/platform/lang-impl/src/com/intellij/execution/services/ServiceViewDragHelper.java b/platform/lang-impl/src/com/intellij/execution/services/ServiceViewDragHelper.java new file mode 100644 index 000000000000..17367fe3ac0d --- /dev/null +++ b/platform/lang-impl/src/com/intellij/execution/services/ServiceViewDragHelper.java @@ -0,0 +1,153 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.execution.services; + +import com.intellij.execution.services.ServiceModel.ServiceViewItem; +import com.intellij.ide.dnd.DnDAction; +import com.intellij.ide.dnd.DnDDragStartBean; +import com.intellij.ide.dnd.DnDSource; +import com.intellij.ide.projectView.PresentationData; +import com.intellij.ide.util.treeView.PresentableNodeDescriptor; +import com.intellij.navigation.ItemPresentation; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.ui.SimpleColoredComponent; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.List; + +class ServiceViewDragHelper { + static DnDSource createSource(ServiceView serviceView) { + return new ServiceViewDnDSource(serviceView); + } + + static String getDisplayName(ItemPresentation presentation) { + StringBuilder result = new StringBuilder(); + if (presentation instanceof PresentationData) { + List fragments = ((PresentationData)presentation).getColoredText(); + if (fragments.isEmpty() && presentation.getPresentableText() != null) { + result.append(presentation.getPresentableText()); + } + else { + for (PresentableNodeDescriptor.ColoredFragment fragment : fragments) { + result.append(fragment.getText()); + } + } + } + else if (presentation.getPresentableText() != null) { + result.append(presentation.getPresentableText()); + } + return result.toString(); + } + + @Nullable + private static ServiceViewContributor getTheOnlyContributor(List nodes) { + ServiceViewContributor result = null; + for (ServiceViewItem node : nodes) { + if (result == null) { + result = node.getContributor(); + } + else if (result != node.getContributor()) { + return null; + } + } + return result; + } + + static class ServiceViewDragBean { + private final List myItems; + private final ServiceViewContributor myContributor; + + ServiceViewDragBean(List items) { + myItems = ContainerUtil.filter(items, item -> { + ServiceViewItem parent = item.getParent(); + while (parent != null) { + if (items.contains(parent)) { + return false; + } + parent = parent.getParent(); + } + return true; + }); + myContributor = getTheOnlyContributor(myItems); + } + + List getItems() { + return myItems; + } + + @Nullable + ServiceViewContributor getContributor() { + return myContributor; + } + } + + private static class ServiceViewDnDSource implements DnDSource { + private final ServiceView myServiceView; + + ServiceViewDnDSource(@NotNull ServiceView serviceView) { + myServiceView = serviceView; + } + + @Override + public boolean canStartDragging(DnDAction action, Point dragOrigin) { + return !myServiceView.getSelectedItems().isEmpty(); + } + + @Override + public DnDDragStartBean startDragging(DnDAction action, Point dragOrigin) { + return new DnDDragStartBean(new ServiceViewDragBean(myServiceView.getSelectedItems())); + } + + @Override + public void dragDropEnd() { + } + + @Override + public void dropActionChanged(int gestureModifiers) { + } + + @Override + public Pair createDraggedImage(DnDAction action, + Point dragOrigin, + @NotNull DnDDragStartBean bean) { + ServiceViewDragBean dragBean = (ServiceViewDragBean)bean.getAttachedObject(); + int size = dragBean.getItems().size(); + ItemPresentation presentation = null; + if (size == 1) { + presentation = dragBean.getItems().get(0).getViewDescriptor().getPresentation(); + } + else { + ServiceViewContributor contributor = dragBean.getContributor(); + if (contributor != null) { + presentation = contributor.getViewDescriptor().getPresentation(); + } + } + + SimpleColoredComponent c = new SimpleColoredComponent(); + c.setForeground(myServiceView.getForeground()); + c.setBackground(myServiceView.getBackground()); + if (presentation != null) { + c.setIcon(presentation.getIcon(false)); + c.append(getDisplayName(presentation)); + } + else { + String text = size + StringUtil.pluralize(" item", size); + c.append(text); + } + + Dimension preferredSize = c.getPreferredSize(); + c.setSize(preferredSize); + BufferedImage image = UIUtil.createImage(c, preferredSize.width, preferredSize.height, BufferedImage.TYPE_INT_ARGB); + c.setOpaque(false); + Graphics2D g = image.createGraphics(); + c.paint(g); + g.dispose(); + return Pair.create(image, new Point(0, 0)); + } + } +} diff --git a/platform/lang-impl/src/com/intellij/execution/services/ServiceViewItem.java b/platform/lang-impl/src/com/intellij/execution/services/ServiceViewItem.java deleted file mode 100644 index 18bde2d72c0d..000000000000 --- a/platform/lang-impl/src/com/intellij/execution/services/ServiceViewItem.java +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -package com.intellij.execution.services; - -import org.jetbrains.annotations.NotNull; - -interface ServiceViewItem { - @NotNull - Object getValue(); - - @NotNull - ServiceViewDescriptor getViewDescriptor(); -} diff --git a/platform/lang-impl/src/com/intellij/execution/services/ServiceViewManagerImpl.java b/platform/lang-impl/src/com/intellij/execution/services/ServiceViewManagerImpl.java index 6f07bea1f2bb..71f262d0a710 100644 --- a/platform/lang-impl/src/com/intellij/execution/services/ServiceViewManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/execution/services/ServiceViewManagerImpl.java @@ -1,9 +1,18 @@ // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.execution.services; +import com.intellij.execution.services.ServiceModel.ServiceGroupNode; +import com.intellij.execution.services.ServiceModel.ServiceViewItem; +import com.intellij.execution.services.ServiceViewDragHelper.ServiceViewDragBean; +import com.intellij.execution.services.ServiceViewModel.*; import com.intellij.icons.AllIcons; +import com.intellij.ide.dnd.DnDDropHandler; +import com.intellij.ide.dnd.DnDEvent; +import com.intellij.ide.dnd.DnDSupport; +import com.intellij.ide.dnd.DnDTargetChecker; import com.intellij.ide.util.PropertiesComponent; import com.intellij.ide.util.treeView.TreeState; +import com.intellij.navigation.ItemPresentation; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.DefaultActionGroup; @@ -12,22 +21,23 @@ import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; import com.intellij.openapi.components.StoragePathMacros; -import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.openapi.fileEditor.FileEditor; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.wm.ToolWindow; import com.intellij.openapi.wm.ToolWindowAnchor; import com.intellij.openapi.wm.ToolWindowId; import com.intellij.openapi.wm.ToolWindowManager; import com.intellij.openapi.wm.ex.ToolWindowEx; +import com.intellij.openapi.wm.impl.InternalDecorator; import com.intellij.ui.AppUIUtil; import com.intellij.ui.AutoScrollFromSourceHandler; -import com.intellij.ui.content.Content; -import com.intellij.ui.content.ContentFactory; +import com.intellij.ui.content.*; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.xmlb.annotations.Property; import org.jdom.Element; import org.jetbrains.annotations.NonNls; @@ -36,22 +46,30 @@ import org.jetbrains.concurrency.AsyncPromise; import org.jetbrains.concurrency.Promise; import javax.swing.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; @State(name = "ServiceViewManager", storages = @Storage(StoragePathMacros.WORKSPACE_FILE)) public final class ServiceViewManagerImpl implements ServiceViewManager, PersistentStateComponent { private static final String AUTO_SCROLL_TO_SOURCE_PROPERTY = "service.view.auto.scroll.to.source"; private static final String AUTO_SCROLL_FROM_SOURCE_PROPERTY = "service.view.auto.scroll.from.source"; - static final ExtensionPointName EP_NAME = - ExtensionPointName.create("com.intellij.serviceViewContributor"); - // TODO [konstantin.aleev] provide help id @NonNls private static final String HELP_ID = "run-dashboard.reference"; - @NotNull private final Project myProject; + private final Project myProject; + private State myState = new State(); - @NotNull private State myState = new State(); - private ServiceView myServiceView; + private ServiceModel myModel; + private ServiceModelFilter myModelFilter; + private final List myServiceViews = ContainerUtil.newSmartList(); + + private ContentManager myContentManager; + private final Content myDropTargetContent = createDropTargetContent(); public ServiceViewManagerImpl(@NotNull Project project) { myProject = project; @@ -59,31 +77,101 @@ public final class ServiceViewManagerImpl implements ServiceViewManager, Persist } boolean hasServices() { - for (ServiceViewContributor contributor : EP_NAME.getExtensions()) { + for (ServiceViewContributor contributor : ServiceModel.EP_NAME.getExtensions()) { if (!contributor.getServices(myProject).isEmpty()) return true; } return false; } void createToolWindowContent(@NotNull ToolWindow toolWindow) { - ServiceViewModel model = new ServiceViewModel.AllServicesModel(myProject); - myProject.getMessageBus().connect(model).subscribe(ServiceEventListener.TOPIC, model::refresh); - myServiceView = ServiceView.createTreeView(myProject, model, myState.viewState); + myModel = new ServiceModel(myProject); + myModelFilter = new ServiceModelFilter(); + myProject.getMessageBus().connect(myModel).subscribe(ServiceEventListener.TOPIC, e -> myModel.refresh(e).onSuccess(o -> { + for (ServiceView serviceView : myServiceViews) { + serviceView.getModel().eventProcessed(e); + } + })); + ServiceView serviceView = ServiceView.createTreeView(myProject, new AllServicesModel(myModel, myModelFilter), myState.viewState); + myServiceViews.add(serviceView); - Content toolWindowContent = ContentFactory.SERVICE.getInstance().createContent(myServiceView, null, false); - toolWindowContent.putUserData(ToolWindow.SHOW_CONTENT_ICON, Boolean.TRUE); - toolWindowContent.setHelpId(getToolWindowContextHelpId()); - toolWindowContent.setCloseable(false); + Content allServicesContent = ContentFactory.SERVICE.getInstance().createContent(serviceView, null, false); + allServicesContent.putUserData(ToolWindow.SHOW_CONTENT_ICON, Boolean.TRUE); + allServicesContent.setHelpId(getToolWindowContextHelpId()); + allServicesContent.setCloseable(false); - Disposer.register(toolWindowContent, myServiceView); - Disposer.register(toolWindowContent, model); - Disposer.register(toolWindowContent, () -> myServiceView = null); + Disposer.register(allServicesContent, myModel); + Disposer.register(allServicesContent, serviceView); + Disposer.register(allServicesContent, serviceView.getModel()); + Disposer.register(allServicesContent, () -> { + myServiceViews.clear(); + myContentManager = null; + myModel = null; + myModelFilter = null; + }); - toolWindow.getContentManager().addContent(toolWindowContent); + myContentManager = toolWindow.getContentManager(); + myContentManager.addContent(allServicesContent); + myContentManager.addContentManagerListener(new MyContentMangerListener(allServicesContent)); ToolWindowEx toolWindowEx = (ToolWindowEx)toolWindow; toolWindowEx.setAdditionalGearActions(new DefaultActionGroup(ToggleAutoScrollAction.toSource(), ToggleAutoScrollAction.fromSource())); toolWindowEx.setTitleActions(ServiceViewAutoScrollFromSourceHandler.install(myProject, toolWindow)); + installDnDSupport(toolWindowEx.getDecorator()); + } + + private void installDnDSupport(InternalDecorator decorator) { + DnDSupport.createBuilder(decorator) + .setTargetChecker(new DnDTargetChecker() { + @Override + public boolean update(DnDEvent event) { + Object o = event.getAttachedObject(); + boolean dropPossible = o instanceof ServiceViewDragBean && event.getPoint().y < decorator.getHeaderHeight(); + event.setDropPossible(dropPossible, ""); + if (dropPossible) { + if (myContentManager.getIndexOfContent(myDropTargetContent) < 0) { + myContentManager.addContent(myDropTargetContent); + } + + ServiceViewDragBean dragBean = (ServiceViewDragBean)o; + ItemPresentation presentation; + if (dragBean.getItems().size() > 1 && dragBean.getContributor() != null) { + presentation = dragBean.getContributor().getViewDescriptor().getPresentation(); + } + else { + presentation = dragBean.getItems().get(0).getViewDescriptor().getPresentation(); + } + myDropTargetContent.setDisplayName(ServiceViewDragHelper.getDisplayName(presentation)); + myDropTargetContent.setIcon(presentation.getIcon(false)); + } + else if (myContentManager.getIndexOfContent(myDropTargetContent) >= 0) { + myContentManager.removeContent(myDropTargetContent, false); + } + return true; + } + }) + .setCleanUpOnLeaveCallback(() -> { + if (myContentManager.getIndexOfContent(myDropTargetContent) >= 0) { + myContentManager.removeContent(myDropTargetContent, false); + } + }) + .setDropHandler(new DnDDropHandler() { + @Override + public void drop(DnDEvent event) { + Object o = event.getAttachedObject(); + if (o instanceof ServiceViewDragBean) { + extract((ServiceViewDragBean)o); + } + } + }) + .install(); + decorator.addMouseMotionListener(new MouseAdapter() { + @Override + public void mouseExited(MouseEvent e) { + if (myContentManager.getIndexOfContent(myDropTargetContent) >= 0) { + myContentManager.removeContent(myDropTargetContent, false); + } + } + }); } @NotNull @@ -92,13 +180,16 @@ public final class ServiceViewManagerImpl implements ServiceViewManager, Persist AsyncPromise result = new AsyncPromise<>(); AppUIUtil.invokeLaterIfProjectAlive(myProject, () -> { Runnable runnable = () -> { - ServiceView view = myServiceView; - if (view == null) { + ContentManager contentManager = myContentManager; + List contents = + contentManager == null ? Collections.emptyList() : ContainerUtil.newSmartList(contentManager.getContents()); + if (contents.isEmpty()) { result.setError("Content not initialized"); + return; } - else { - view.select(service, contributorClass).processed(result); - } + + Collections.reverse(contents); + select(myProject, contents.iterator(), result, service, contributorClass); }; ToolWindow window = activate ? ToolWindowManager.getInstance(myProject).getToolWindow(ToolWindowId.SERVICES) : null; if (window != null) { @@ -111,6 +202,32 @@ public final class ServiceViewManagerImpl implements ServiceViewManager, Persist return result; } + private static void select(Project project, Iterator iterator, AsyncPromise result, + @NotNull Object service, @NotNull Class contributorClass) { + Content content = iterator.next(); + ((ServiceView)content.getComponent()).select(service, contributorClass) + .onSuccess(v -> { + AppUIUtil.invokeOnEdt(() -> { + ContentManager contentManager = content.getManager(); + if (contentManager == null) return; + + if (contentManager.getSelectedContent() != content && contentManager.getIndexOfContent(content) >= 0) { + contentManager.setSelectedContent(content); + } + }, project.getDisposed()); + + result.setResult(null); + }) + .onError(e -> { + if (iterator.hasNext()) { + select(project, iterator, result, service, contributorClass); + } + else { + result.setError(e); + } + }); + } + private void updateToolWindow(ServiceEventListener.ServiceEvent event) { ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(myProject); if (toolWindowManager == null) return; @@ -148,10 +265,147 @@ public final class ServiceViewManagerImpl implements ServiceViewManager, Persist return toolWindow; } + void extract(@NotNull ServiceViewDragBean dragBean) { + List items = dragBean.getItems(); + if (items.isEmpty()) return; + + ServiceViewContributor contributor = dragBean.getContributor(); + if (contributor != null) { + List roots = myModel.getRoots(); + List contributorRoots = ContainerUtil.filter(roots, node -> contributor == node.getContributor()); + if (contributorRoots.equals(items)) { + extractContributor(contributor); + return; + } + } + + if (items.size() == 1) { + ServiceViewItem item = items.get(0); + if (item instanceof ServiceGroupNode) { + extractGroup((ServiceGroupNode)item); + } + else { + extractService(item); + } + return; + } + extractList(items, -1); + } + + private void extractContributor(ServiceViewContributor contributor) { + ServiceView serviceView = + ServiceView.createTreeView(myProject, + new ContributorModel(myModel, myModelFilter, contributor, getSelectedView().getModel().getFilter()), + myState.viewState); + ItemPresentation presentation = contributor.getViewDescriptor().getContentPresentation(); + addServiceView(serviceView, ServiceViewDragHelper.getDisplayName(presentation), presentation.getIcon(false)); + } + + private void extractGroup(ServiceGroupNode group) { + AtomicReference ref = new AtomicReference<>(group); + ServiceView serviceView = + ServiceView.createTreeView(myProject, + new GroupModel(myModel, myModelFilter, ref, getSelectedView().getModel().getFilter()), + myState.viewState); + + ItemPresentation presentation = group.getViewDescriptor().getContentPresentation(); + Content content = addServiceView(serviceView, ServiceViewDragHelper.getDisplayName(presentation), presentation.getIcon(false)); + serviceView.getModel().addModelListener(() -> updateContentTab(ref.get(), content)); + } + + private void extractService(ServiceViewItem service) { + AtomicReference ref = new AtomicReference<>(service); + ServiceView serviceView = + ServiceView.createSingleView(myProject, + new SingeServiceModel(myModel, myModelFilter, ref, getSelectedView().getModel().getFilter())); + + ItemPresentation presentation = service.getViewDescriptor().getContentPresentation(); + Content content = addServiceView(serviceView, ServiceViewDragHelper.getDisplayName(presentation), presentation.getIcon(false)); + serviceView.getModel().addModelListener(() -> { + ServiceViewItem item = ref.get(); + if (item != null && !serviceView.getModel().getChildren(item).isEmpty()) { + AppUIUtil.invokeOnEdt(() -> { + int index = myContentManager.getIndexOfContent(content); + myContentManager.removeContent(content, true); + extractList(ContainerUtil.newSmartList(item), index); + }, myProject.getDisposed()); + } + else { + updateContentTab(item, content); + } + }); + } + + private void extractList(List items, int index) { + String name; + Icon icon; + if (items.size() == 1) { + ItemPresentation presentation = items.get(0).getViewDescriptor().getContentPresentation(); + name = ServiceViewDragHelper.getDisplayName(presentation); + icon = presentation.getIcon(false); + } + else { + name = Messages.showInputDialog(myProject, "Group Name:", "Group Services", null, null, null); + if (name == null) return; + + icon = AllIcons.Nodes.Folder; + } + + ServiceView serviceView = + ServiceView.createTreeView(myProject, + new ServiceListModel(myModel, myModelFilter, items, getSelectedView().getModel().getFilter()), + myState.viewState); + Content content = addServiceView(serviceView, name, icon, index); + serviceView.getModel().addModelListener(() -> updateContentTab(ContainerUtil.getOnlyItem(serviceView.getModel().getRoots()), content)); + } + + private Content addServiceView(ServiceView serviceView, String displayName, Icon icon) { + return addServiceView(serviceView, displayName, icon, -1); + } + + private Content addServiceView(ServiceView serviceView, String displayName, Icon icon, int index) { + myServiceViews.add(index == -1 ? myServiceViews.size() : index, serviceView); + myModelFilter.addFilter(serviceView.getModel().getFilter()); + + Content content = ContentFactory.SERVICE.getInstance().createContent(serviceView, displayName, false); + content.putUserData(ToolWindow.SHOW_CONTENT_ICON, Boolean.TRUE); + content.setHelpId(getToolWindowContextHelpId()); + content.setCloseable(true); + content.setIcon(icon); + + Disposer.register(content, serviceView); + Disposer.register(content, serviceView.getModel()); + + ServiceView selectedView = getSelectedView(); + myContentManager.addContent(content, index); + myContentManager.setSelectedContent(content); + myModel.getInvoker().invokeLater(() -> selectedView.getModel().filtersChanged()); + serviceView.getModel().addModelListener(() -> { + if (serviceView.getModel().getRoots().isEmpty()) { + AppUIUtil.invokeOnEdt(() -> myContentManager.removeContent(content, true), myProject.getDisposed()); + } + }); + return content; + } + + private ServiceView getSelectedView() { + return (ServiceView)myContentManager.getSelectedContent().getComponent(); + } + + private void updateContentTab(ServiceViewItem item, Content content) { + if (item != null) { + ItemPresentation itemPresentation = item.getViewDescriptor().getContentPresentation(); + AppUIUtil.invokeOnEdt(() -> { + content.setDisplayName(ServiceViewDragHelper.getDisplayName(itemPresentation)); + content.setIcon(itemPresentation.getIcon(false)); + }, myProject.getDisposed()); + } + } + @NotNull @Override public State getState() { - ServiceView serviceView = myServiceView; + ServiceView serviceView = ContainerUtil.getFirstItem(myServiceViews); if (serviceView != null) { serviceView.saveState(myState.viewState); myState.viewState.treeStateElement = new Element("root"); @@ -171,14 +425,6 @@ public final class ServiceViewManagerImpl implements ServiceViewManager, Persist public ServiceViewState viewState = new ServiceViewState(); } - static boolean isAutoScrollToSourceEnabled(@NotNull Project project) { - return PropertiesComponent.getInstance(project).getBoolean(AUTO_SCROLL_TO_SOURCE_PROPERTY); - } - - private static boolean isAutoScrollFromSourceEnabled(@NotNull Project project) { - return PropertiesComponent.getInstance(project).getBoolean(AUTO_SCROLL_FROM_SOURCE_PROPERTY); - } - private static String getToolWindowId() { return ToolWindowId.SERVICES; } @@ -191,6 +437,67 @@ public final class ServiceViewManagerImpl implements ServiceViewManager, Persist return HELP_ID; } + private static Content createDropTargetContent() { + Content content = ContentFactory.SERVICE.getInstance().createContent(new JPanel(), null, false); + content.putUserData(ToolWindow.SHOW_CONTENT_ICON, Boolean.TRUE); + content.setCloseable(true); + return content; + } + + private class MyContentMangerListener extends ContentManagerAdapter { + private final Content myAllServiceContent; + + private MyContentMangerListener(Content allServiceContent) { + myAllServiceContent = allServiceContent; + } + + @Override + public void contentAdded(@NotNull ContentManagerEvent event) { + if (myContentManager.getContentCount() > 1) { + myAllServiceContent.setDisplayName("All Services"); + } + } + + @Override + public void contentRemoved(@NotNull ContentManagerEvent event) { + if (event.getContent() != myDropTargetContent) { + ServiceView removedView = (ServiceView)event.getContent().getComponent(); + myModelFilter.removeFilter(removedView.getModel().getFilter()); + myServiceViews.remove(removedView); + myModel.getInvoker().invokeLater(() -> { + for (ServiceView serviceView : myServiceViews) { + serviceView.getModel().filtersChanged(); + } + }); + } + if (myContentManager.getContentCount() == 1) { + myAllServiceContent.setDisplayName(null); + } + } + + @Override + public void selectionChanged(@NotNull ContentManagerEvent event) { + if (event.getContent() == myDropTargetContent) return; + + ServiceView serviceView = (ServiceView)event.getContent().getComponent(); + if (event.getOperation() == ContentManagerEvent.ContentOperation.add) { + serviceView.onViewSelected(); + } + else { + serviceView.onViewUnselected(); + } + } + } + + + static boolean isAutoScrollToSourceEnabled(@NotNull Project project) { + return PropertiesComponent.getInstance(project).getBoolean(AUTO_SCROLL_TO_SOURCE_PROPERTY); + } + + private static boolean isAutoScrollFromSourceEnabled(@NotNull Project project) { + return PropertiesComponent.getInstance(project).getBoolean(AUTO_SCROLL_FROM_SOURCE_PROPERTY); + } + private static class ToggleAutoScrollAction extends ToggleAction implements DumbAware { private final String myProperty; @@ -243,7 +550,7 @@ public final class ServiceViewManagerImpl implements ServiceViewManager, Persist } private boolean select(@NotNull FileEditor editor) { - for (ServiceViewContributor extension : EP_NAME.getExtensions()) { + for (ServiceViewContributor extension : ServiceModel.EP_NAME.getExtensions()) { if (!(extension instanceof ServiceViewFileEditorContributor)) continue; if (selectContributorNode(editor, (ServiceViewFileEditorContributor)extension, extension.getClass())) return true; } diff --git a/platform/lang-impl/src/com/intellij/execution/services/ServiceViewModel.java b/platform/lang-impl/src/com/intellij/execution/services/ServiceViewModel.java index d2765305a518..0a4dc0c65763 100644 --- a/platform/lang-impl/src/com/intellij/execution/services/ServiceViewModel.java +++ b/platform/lang-impl/src/com/intellij/execution/services/ServiceViewModel.java @@ -1,9 +1,11 @@ // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.execution.services; -import com.intellij.ide.util.treeView.NodeDescriptor; +import com.intellij.execution.services.ServiceEventListener.ServiceEvent; +import com.intellij.execution.services.ServiceModel.ServiceGroupNode; +import com.intellij.execution.services.ServiceModel.ServiceViewItem; +import com.intellij.execution.services.ServiceModelFilter.ServiceViewFilter; import com.intellij.openapi.Disposable; -import com.intellij.openapi.project.Project; import com.intellij.util.concurrency.Invoker; import com.intellij.util.concurrency.InvokerSupplier; import com.intellij.util.containers.ContainerUtil; @@ -11,264 +13,260 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; +import java.util.concurrent.atomic.AtomicReference; -interface ServiceViewModel extends Disposable, InvokerSupplier { - List getRoots(); +abstract class ServiceViewModel implements Disposable, InvokerSupplier { + protected final ServiceModel myModel; + protected final ServiceModelFilter myModelFilter; + private final ServiceViewFilter myFilter; + private final List myListeners = ContainerUtil.newSmartList(); - void refresh(@NotNull ServiceEventListener.ServiceEvent e); + protected ServiceViewModel(@NotNull ServiceModel model, @NotNull ServiceModelFilter modelFilter, ServiceViewFilter condition) { + myModel = model; + myModelFilter = modelFilter; + myFilter = condition; + } - void addModelListener(ServiceViewModelListener listener); + @NotNull + List getRoots() { + return filterEmptyGroups(doGetRoots()); + } - void removeModelListener(ServiceViewModelListener listener); + @NotNull + protected abstract List doGetRoots(); + + abstract void eventProcessed(ServiceEvent e); + + void filtersChanged() { + notifyListeners(); + } + + ServiceViewFilter getFilter() { + return myFilter; + } + + @NotNull + List getChildren(@NotNull ServiceViewItem parent) { + return filterEmptyGroups(myModelFilter.filter(parent.getChildren(), myFilter)); + } + + void addModelListener(@NotNull ServiceViewModelListener listener) { + myListeners.add(listener); + } + + void removeModelListener(@NotNull ServiceViewModelListener listener) { + myListeners.remove(listener); + } + + protected void notifyListeners() { + for (ServiceViewModelListener listener : myListeners) { + listener.rootsChanged(); + } + } + + @Override + public void dispose() { + } + + @NotNull + @Override + public Invoker getInvoker() { + return myModel.getInvoker(); + } + + @NotNull + private List filterEmptyGroups(@NotNull List items) { + return ContainerUtil.filter(items, item -> !(item instanceof ServiceGroupNode) || !getChildren(item).isEmpty()); + } + + @Nullable + private static ServiceViewItem findItem(ServiceViewItem viewItem, List modelItems) { + return findItem(getPath(viewItem), modelItems); + } + + @Nullable + private static ServiceViewItem findItem(Deque path, List modelItems) { + ServiceViewItem node = path.removeFirst(); + for (ServiceViewItem root : modelItems) { + if (root.equals(node)) { + if (path.isEmpty()) { + return root; + } + else { + return findItem(path, root.getChildren()); + } + } + } + return null; + } + + private static Deque getPath(ServiceViewItem item) { + Deque path = new LinkedList<>(); + do { + path.addFirst(item); + item = item.getParent(); + } + while (item != null); + return path; + } interface ServiceViewModelListener { void rootsChanged(); } - static List getContributorChildren(Project project, - ServiceTreeNode parent, - ServiceViewContributor contributor) { - Set children = new LinkedHashSet<>(); - Map groupNodes = new HashMap<>(); - for (T service : contributor.getServices(project)) { - Object value = service instanceof ServiceViewProvidingContributor ? ((ServiceViewProvidingContributor)service).asService() : service; - - if (contributor instanceof ServiceViewGroupingContributor) { - ServiceViewGroupingContributor groupingContributor = (ServiceViewGroupingContributor)contributor; - Object group = groupingContributor.groupBy(service); - if (group != null) { - ServiceGroupNode groupNode = groupNodes.get(group); - if (groupNode == null) { - groupNode = new ServiceGroupNode(group, parent, contributor, groupingContributor.getGroupDescriptor(group)); - groupNodes.put(group, groupNode); - } - ServiceTreeNode serviceNode = new ServiceNode(value, groupNode, contributor, contributor.getServiceDescriptor(service), project, - service instanceof ServiceViewContributor ? (ServiceViewContributor)service : null); - groupNode.getChildren().add(serviceNode); - children.add(groupNode); - continue; - } - } - - ServiceTreeNode serviceNode = new ServiceNode(value, parent, contributor, contributor.getServiceDescriptor(service), project, - service instanceof ServiceViewContributor ? (ServiceViewContributor)service : null); - children.add(serviceNode); - } - return new ArrayList<>(children); - } - - class AllServicesModel implements ServiceViewModel { - private final Project myProject; - private final Invoker myInvoker = new Invoker.BackgroundThread(this); - private final List myListeners = ContainerUtil.newSmartList(); - private final List myRoots = new ArrayList<>(); - private boolean myRootInitialized; - - public AllServicesModel(Project project) { - myProject = project; + static class AllServicesModel extends ServiceViewModel { + AllServicesModel(@NotNull ServiceModel model, @NotNull ServiceModelFilter modelFilter) { + super(model, modelFilter, null); } @Override - public List getRoots() { - if (!myRootInitialized) { - myRootInitialized = true; - myRoots.clear(); - myRoots.addAll(doGetRoots()); - } - return myRoots; - } - - private List doGetRoots() { - List result = new ArrayList<>(); - for (ServiceViewContributor contributor : ServiceViewManagerImpl.EP_NAME.getExtensions()) { - result.addAll(getContributorChildren(myProject, null, contributor)); - } - return result; - } - - @Override - public void refresh(@NotNull ServiceEventListener.ServiceEvent e) { - getInvoker().runOrInvokeLater(() -> reset(e.contributorClass)); - } - - private void reset(Class contributorClass) { - int startIndex = -1; - - if (myRoots.isEmpty()) { - startIndex = 0; - } - else { - Map indexes = new HashMap<>(); - List toRemove = new ArrayList<>(); - ServiceViewContributor previous = null; - for (int i = 0; i < myRoots.size(); i++) { - ServiceTreeNode child = myRoots.get(i); - if (contributorClass.isInstance(child.getContributor())) { - toRemove.add(child); - if (startIndex < 0) { - startIndex = i; - } - } - else if (previous != child.getContributor()) { - previous = child.getContributor(); - indexes.put(previous, i); - } - } - if (startIndex < 0) { - ServiceViewContributor[] contributors = ServiceViewManagerImpl.EP_NAME.getExtensions(); - for (int i = contributors.length - 1; i >= 0; i--) { - if (!contributorClass.isInstance(contributors[i])) { - startIndex = indexes.getOrDefault(contributors[i], Integer.valueOf(-1)); - if (startIndex == 0) { - break; - } - } - else { - break; - } - } - if (startIndex < 0) { - startIndex = myRoots.size() - toRemove.size(); - } - } - myRoots.removeAll(toRemove); - } - - List newChildren = null; - for (ServiceViewContributor contributor : ServiceViewManagerImpl.EP_NAME.getExtensions()) { - if (contributorClass.isInstance(contributor)) { - newChildren = getContributorChildren(myProject, null, contributor); - break; - } - } - if (newChildren != null) { - myRoots.addAll(startIndex, newChildren); - } - - for (ServiceViewModelListener listener : myListeners) { - listener.rootsChanged(); - } - } - - @Override - public void addModelListener(ServiceViewModelListener listener) { - myListeners.add(listener); - } - - @Override - public void removeModelListener(ServiceViewModelListener listener) { - myListeners.remove(listener); - } - - @Override - public void dispose() { - } - @NotNull + protected List doGetRoots() { + return myModelFilter.filter(myModel.getRoots(), null); + } + @Override - public Invoker getInvoker() { - return myInvoker; + void eventProcessed(ServiceEvent e) { + notifyListeners(); } } - abstract class ServiceTreeNode implements ServiceViewItem { - private final Object myValue; - private final ServiceTreeNode myParent; + static class ContributorModel extends ServiceViewModel { private final ServiceViewContributor myContributor; - private final ServiceViewDescriptor myViewDescriptor; - private List myChildren; - private boolean myPresentationUpdated; - protected ServiceTreeNode(@NotNull Object value, @Nullable ServiceTreeNode parent, @NotNull ServiceViewContributor contributor, - @NotNull ServiceViewDescriptor viewDescriptor) { - myValue = value; - myParent = parent; - myContributor = contributor; - myViewDescriptor = viewDescriptor; - } - - @NotNull - @Override - public Object getValue() { - return myValue; - } - - @NotNull - ServiceViewContributor getContributor() { - return myContributor; - } - - @NotNull - @Override - public ServiceViewDescriptor getViewDescriptor() { - if (!myPresentationUpdated) { - myPresentationUpdated = true; - if (myValue instanceof NodeDescriptor) { - ((NodeDescriptor)myValue).update(); + ContributorModel(@NotNull ServiceModel model, @NotNull ServiceModelFilter modelFilter, @NotNull ServiceViewContributor contributor, + @Nullable ServiceViewFilter parentFilter) { + super(model, modelFilter, new ServiceViewFilter(parentFilter) { + @Override + public boolean value(ServiceViewItem item) { + return contributor.equals(item.getContributor()); } - } - return myViewDescriptor; - } - - @Nullable - ServiceTreeNode getParent() { - return myParent; + }); + myContributor = contributor; } @NotNull - List getChildren() { - if (myChildren == null) { - myChildren = doGetChildren(); + @Override + protected List doGetRoots() { + return myModelFilter.filter(ContainerUtil.filter(myModel.getRoots(), getFilter()), getFilter()); + } + + @Override + void eventProcessed(ServiceEvent e) { + if (e.contributorClass.isInstance(myContributor)) { + notifyListeners(); } - return myChildren; - } - - @NotNull - abstract List doGetChildren(); - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ServiceTreeNode node = (ServiceTreeNode)o; - return myValue.equals(node.myValue); - } - - @Override - public int hashCode() { - return myValue.hashCode(); } } - class ServiceNode extends ServiceTreeNode { - private final Project myProject; - private final ServiceViewContributor myProvidingContributor; + static class GroupModel extends ServiceViewModel { + private final AtomicReference myGroupRef; - ServiceNode(@NotNull Object service, @Nullable ServiceTreeNode parent, @NotNull ServiceViewContributor contributor, - @NotNull ServiceViewDescriptor viewDescriptor, - @NotNull Project project, @Nullable ServiceViewContributor providingContributor) { - super(service, parent, contributor, viewDescriptor); - myProject = project; - myProvidingContributor = providingContributor; + GroupModel(@NotNull ServiceModel model, @NotNull ServiceModelFilter modelFilter, + @NotNull AtomicReference groupRef, @Nullable ServiceViewFilter parentFilter) { + super(model, modelFilter, new ServiceViewFilter(parentFilter) { + @Override + public boolean value(ServiceViewItem item) { + ServiceViewItem parent = item.getParent(); + return parent != null && parent.equals(groupRef.get()); + } + }); + myGroupRef = groupRef; } @NotNull @Override - List doGetChildren() { - return myProvidingContributor == null ? Collections.emptyList() : getContributorChildren(myProject, this, myProvidingContributor); + protected List doGetRoots() { + ServiceGroupNode group = myGroupRef.get(); + return group == null ? Collections.emptyList() : getChildren(group); + } + + @Override + void eventProcessed(ServiceEvent e) { + ServiceGroupNode group = myGroupRef.get(); + if (group == null || !e.contributorClass.isInstance(group.getRootContributor())) return; + + myGroupRef.set((ServiceGroupNode)findItem(group, myModel.getRoots())); + notifyListeners(); } } - class ServiceGroupNode extends ServiceTreeNode { - ServiceGroupNode(@NotNull Object group, @Nullable ServiceTreeNode parent, @NotNull ServiceViewContributor contributor, - @NotNull ServiceViewDescriptor viewDescriptor) { - super(group, parent, contributor, viewDescriptor); + static class SingeServiceModel extends ServiceViewModel { + private final AtomicReference myServiceRef; + + SingeServiceModel(@NotNull ServiceModel model, @NotNull ServiceModelFilter modelFilter, + @NotNull AtomicReference serviceRef, @Nullable ServiceViewFilter parentFilter) { + super(model, modelFilter, new ServiceViewFilter(parentFilter) { + @Override + public boolean value(ServiceViewItem item) { + return item.equals(serviceRef.get()); + } + }); + myServiceRef = serviceRef; } @NotNull @Override - List doGetChildren() { - return new ArrayList<>(); + protected List doGetRoots() { + ServiceViewItem service = myServiceRef.get(); + return service == null ? Collections.emptyList() : Collections.singletonList(service); + } + + @Override + void eventProcessed(ServiceEvent e) { + ServiceViewItem service = myServiceRef.get(); + if (service == null || !e.contributorClass.isInstance(service.getRootContributor())) return; + + myServiceRef.set(findItem(service, myModel.getRoots())); + notifyListeners(); + } + } + + static class ServiceListModel extends ServiceViewModel { + private final List myRoots; + + ServiceListModel(@NotNull ServiceModel model, @NotNull ServiceModelFilter modelFilter, @NotNull List roots, + @Nullable ServiceViewFilter parentFilter) { + super(model, modelFilter, new ServiceViewFilter(parentFilter) { + @Override + public boolean value(ServiceViewItem item) { + return roots.contains(item); + } + }); + myRoots = roots; + } + + @NotNull + @Override + protected List doGetRoots() { + return myModelFilter.filter(myRoots, getFilter()); + } + + @Override + void eventProcessed(ServiceEvent e) { + boolean update = false; + + List toRemove = new ArrayList<>(); + for (int i = 0; i < myRoots.size(); i++) { + ServiceViewItem node = myRoots.get(i); + if (!e.contributorClass.isInstance(node.getRootContributor())) continue; + + ServiceViewItem updatedNode = findItem(node, myModel.getRoots()); + if (updatedNode != null) { + //noinspection SuspiciousListRemoveInLoop + myRoots.remove(i); + myRoots.add(i, updatedNode); + } + else { + toRemove.add(node); + } + update = true; + } + myRoots.removeAll(toRemove); + + if (update) { + notifyListeners(); + } } } } diff --git a/platform/lang-impl/src/com/intellij/execution/services/ServiceViewSingleUi.java b/platform/lang-impl/src/com/intellij/execution/services/ServiceViewSingleUi.java new file mode 100644 index 000000000000..7b8cd5221104 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/execution/services/ServiceViewSingleUi.java @@ -0,0 +1,43 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.execution.services; + +import com.intellij.openapi.ui.SimpleToolWindowPanel; +import com.intellij.ui.components.JBPanelWithEmptyText; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; + +public class ServiceViewSingleUi implements ServiceViewUi { + private final SimpleToolWindowPanel myMainPanel = new SimpleToolWindowPanel(false); + private final JPanel myMessagePanel = new JBPanelWithEmptyText().withEmptyText("No content available"); + + @NotNull + @Override + public JComponent getComponent() { + return myMainPanel; + } + + @Override + public void saveState(@NotNull ServiceViewState state) { + } + + @Override + public void setServiceToolbar(@NotNull ServiceViewActionProvider actionManager) { + myMainPanel.setToolbar(actionManager.createServiceToolbar(myMainPanel)); + } + + @Override + public void setMasterPanel(@NotNull JComponent component, @NotNull ServiceViewActionProvider actionManager) { + } + + @Override + public void setDetailsComponent(@Nullable JComponent component) { + if (component == null) { + component = myMessagePanel; + } + if (component.getParent() == myMainPanel) return; + + myMainPanel.setContent(component); + } +} diff --git a/platform/lang-impl/src/com/intellij/execution/services/ServiceViewTree.java b/platform/lang-impl/src/com/intellij/execution/services/ServiceViewTree.java index a19a2bdd7e76..9d33c7aba067 100644 --- a/platform/lang-impl/src/com/intellij/execution/services/ServiceViewTree.java +++ b/platform/lang-impl/src/com/intellij/execution/services/ServiceViewTree.java @@ -1,6 +1,7 @@ // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.execution.services; +import com.intellij.execution.services.ServiceModel.ServiceViewItem; import com.intellij.ide.projectView.PresentationData; import com.intellij.navigation.ItemPresentation; import com.intellij.openapi.Disposable; @@ -40,8 +41,6 @@ class ServiceViewTree extends Tree { new TreeSpeedSearch(this, TreeSpeedSearch.NODE_DESCRIPTOR_TOSTRING, true); ServiceViewTreeLinkMouseListener mouseListener = new ServiceViewTreeLinkMouseListener(this); mouseListener.installOn(this); - - //RowsDnDSupport.install(myTree, myTreeModel); new DoubleClickListener() { @Override protected boolean onDoubleClick(MouseEvent e) { @@ -54,6 +53,10 @@ class ServiceViewTree extends Tree { ((ServiceViewItem)lastComponent).getViewDescriptor().handleDoubleClick(e); } }.installOn(this); + + // DnD + //RowsDnDSupport.install(myTree, myTreeModel); + setDragEnabled(true); } private static class ServiceViewTreeCellRenderer extends ServiceViewTreeCellRendererBase { diff --git a/platform/lang-impl/src/com/intellij/execution/services/ServiceViewTreeModel.java b/platform/lang-impl/src/com/intellij/execution/services/ServiceViewTreeModel.java index 404cda452ab7..a285fd551d99 100644 --- a/platform/lang-impl/src/com/intellij/execution/services/ServiceViewTreeModel.java +++ b/platform/lang-impl/src/com/intellij/execution/services/ServiceViewTreeModel.java @@ -1,7 +1,8 @@ // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.execution.services; -import com.intellij.execution.services.ServiceViewModel.ServiceTreeNode; +import com.intellij.execution.services.ServiceModel.ServiceViewItem; +import com.intellij.execution.services.ServiceViewModel.ServiceViewModelListener; import com.intellij.ui.tree.BaseTreeModel; import com.intellij.util.ArrayUtil; import com.intellij.util.Function; @@ -15,16 +16,18 @@ import org.jetbrains.concurrency.AsyncPromise; import org.jetbrains.concurrency.Promise; import javax.swing.tree.TreePath; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; class ServiceViewTreeModel extends BaseTreeModel implements InvokerSupplier { private final ServiceViewModel myModel; - private final ServiceViewModel.ServiceViewModelListener myListener; + private final ServiceViewModelListener myListener; private final Object myRoot = ObjectUtils.sentinel("services root"); ServiceViewTreeModel(ServiceViewModel model) { myModel = model; - myListener = new ServiceViewModel.ServiceViewModelListener() { + myListener = new ServiceViewModelListener() { @Override public void rootsChanged() { treeStructureChanged(new TreePath(myRoot), null, null); @@ -46,7 +49,7 @@ class ServiceViewTreeModel extends BaseTreeModel implements InvokerSuppl @Override public boolean isLeaf(Object object) { - return object != myRoot && ((ServiceTreeNode)object).getChildren().isEmpty(); + return object != myRoot && myModel.getChildren(((ServiceViewItem)object)).isEmpty(); } @Override @@ -54,7 +57,7 @@ class ServiceViewTreeModel extends BaseTreeModel implements InvokerSuppl if (parent == myRoot) { return myModel.getRoots(); } - return ((ServiceTreeNode)parent).getChildren(); + return myModel.getChildren(((ServiceViewItem)parent)); } @Override @@ -65,18 +68,20 @@ class ServiceViewTreeModel extends BaseTreeModel implements InvokerSuppl Promise findPath(@NotNull Object service, @NotNull Class contributorClass) { AsyncPromise result = new AsyncPromise<>(); getInvoker().runOrInvokeLater(() -> { - ServiceTreeNode serviceNode = JBTreeTraverser.from((Function>)node -> - contributorClass.isInstance(node.getContributor()) ? node.getChildren() : null) - .withRoots(myModel.getRoots()) + List roots = myModel.getRoots(); + ServiceViewItem serviceNode = JBTreeTraverser.from((Function>)node -> + contributorClass.isInstance(node.getRootContributor()) ? new ArrayList<>(myModel.getChildren(node)) : null) + .withRoots(roots) .traverse(TreeTraversal.PLAIN_BFS) .filter(node -> node.getValue().equals(service)) .first(); if (serviceNode != null) { List path = new ArrayList<>(); - while (serviceNode != null) { + do { path.add(serviceNode); - serviceNode = serviceNode.getParent(); + serviceNode = roots.contains(serviceNode) ? null : serviceNode.getParent(); } + while (serviceNode != null); path.add(myRoot); Collections.reverse(path); result.setResult(new TreePath(ArrayUtil.toObjectArray(path))); diff --git a/platform/platform-resources/src/idea/LangActions.xml b/platform/platform-resources/src/idea/LangActions.xml index ec2b1a65d60e..219d5f655cfa 100644 --- a/platform/platform-resources/src/idea/LangActions.xml +++ b/platform/platform-resources/src/idea/LangActions.xml @@ -322,7 +322,7 @@ - + @@ -330,7 +330,7 @@ - + @@ -1179,6 +1179,8 @@ +