[service-view] extract services to separate tab

GitOrigin-RevId: 9d0c0c594cadf8e248bcd87492231d0a29f59247
This commit is contained in:
Konstantin Aleev
2019-05-10 23:05:57 +03:00
committed by intellij-monorepo-bot
parent 3393b98461
commit cea104758e
17 changed files with 1338 additions and 294 deletions
@@ -21,6 +21,11 @@ public interface ServiceViewDescriptor {
return null;
}
@NotNull
default ItemPresentation getContentPresentation() {
return getPresentation();
}
@Nullable
default ActionGroup getToolbarActions() {
return null;
@@ -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
@@ -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<ServiceViewItem> items = getSelectedItems(e);
e.getPresentation().setEnabled(!items.isEmpty());
}
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
Project project = e.getProject();
if (project == null) return;
List<ServiceViewItem> items = getSelectedItems(e);
if (items.isEmpty()) return;
((ServiceViewManagerImpl)ServiceViewManager.getInstance(e.getProject())).extract(new ServiceViewDragBean(items));
}
private static List<ServiceViewItem> 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();
}
}
@@ -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<ServiceViewContributor> EP_NAME = ExtensionPointName.create("com.intellij.serviceViewContributor");
private final Project myProject;
private final Invoker myInvoker = new Invoker.BackgroundThread(this);
private final List<ServiceViewItem> 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<? extends ServiceViewItem> getRoots() {
if (!myRootInitialized) {
myRootInitialized = true;
myRoots.clear();
myRoots.addAll(doGetRoots());
}
return myRoots;
}
private List<? extends ServiceViewItem> doGetRoots() {
List<ServiceViewItem> 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<ServiceViewContributor, Integer> indexes = new HashMap<>();
List<ServiceViewItem> 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<ServiceViewItem> 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 <T> List<ServiceViewItem> getContributorChildren(Project project,
ServiceViewItem parent,
ServiceViewContributor<T> contributor) {
Set<ServiceViewItem> children = new LinkedHashSet<>();
Map<Object, ServiceGroupNode> groupNodes = new HashMap<>();
for (T service : contributor.getServices(project)) {
Object value = service instanceof ServiceViewProvidingContributor ? ((ServiceViewProvidingContributor)service).asService() : service;
if (contributor instanceof ServiceViewGroupingContributor) {
ServiceViewGroupingContributor<T, Object> groupingContributor = (ServiceViewGroupingContributor<T, Object>)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<ServiceViewItem> 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<ServiceViewItem> getChildren() {
if (myChildren == null) {
myChildren = doGetChildren();
}
return myChildren;
}
@NotNull
abstract List<ServiceViewItem> 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<ServiceViewItem> 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<ServiceViewItem> doGetChildren() {
return new ArrayList<>();
}
}
}
@@ -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<ServiceViewFilter> 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<? extends ServiceViewItem> filter(@NotNull List<? extends ServiceViewItem> items, @Nullable ServiceViewFilter targetFilter) {
if (items.isEmpty()) return items;
List<ServiceViewFilter> filters = targetFilter == null ? myFilters : excludeTargetAndParents(targetFilter);
return ContainerUtil.filter(items, item -> filters.stream().noneMatch(filter -> filter.value(item)));
}
private List<ServiceViewFilter> excludeTargetAndParents(@NotNull ServiceViewFilter targetFilter) {
List<ServiceViewFilter> filters = new ArrayList<>(myFilters);
do {
filters.remove(targetFilter);
targetFilter = targetFilter.getParent();
}
while (targetFilter != null);
return filters;
}
abstract static class ServiceViewFilter implements Condition<ServiceViewItem> {
private ServiceViewFilter myParent;
protected ServiceViewFilter(@Nullable ServiceViewFilter parent) {
myParent = parent;
}
@Nullable
ServiceViewFilter getParent() {
return myParent;
}
private void setParent(@Nullable ServiceViewFilter parent) {
myParent = parent;
}
}
}
@@ -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<ServiceViewItem> 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<Void> 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<ServiceViewItem> 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());
}
}
}
@@ -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<ServiceViewItem> 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<ServiceViewItem> 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<ServiceViewItem> 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();
}
@@ -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<Void> 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)) {
@@ -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;
@@ -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<PresentableNodeDescriptor.ColoredFragment> 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<ServiceViewItem> 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<ServiceViewItem> myItems;
private final ServiceViewContributor myContributor;
ServiceViewDragBean(List<ServiceViewItem> 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<ServiceViewItem> 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<Image, Point> 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));
}
}
}
@@ -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();
}
@@ -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<ServiceViewManagerImpl.State> {
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<ServiceViewContributor> 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<ServiceView> 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<Void> result = new AsyncPromise<>();
AppUIUtil.invokeLaterIfProjectAlive(myProject, () -> {
Runnable runnable = () -> {
ServiceView view = myServiceView;
if (view == null) {
ContentManager contentManager = myContentManager;
List<Content> 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<Content> iterator, AsyncPromise<Void> 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<ServiceViewItem> items = dragBean.getItems();
if (items.isEmpty()) return;
ServiceViewContributor contributor = dragBean.getContributor();
if (contributor != null) {
List<? extends ServiceViewItem> roots = myModel.getRoots();
List<? extends ServiceViewItem> 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<ServiceGroupNode> 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<ServiceViewItem> 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<ServiceViewItem> 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;
}
@@ -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<? extends ServiceTreeNode> getRoots();
abstract class ServiceViewModel implements Disposable, InvokerSupplier {
protected final ServiceModel myModel;
protected final ServiceModelFilter myModelFilter;
private final ServiceViewFilter myFilter;
private final List<ServiceViewModelListener> 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<? extends ServiceViewItem> getRoots() {
return filterEmptyGroups(doGetRoots());
}
void removeModelListener(ServiceViewModelListener listener);
@NotNull
protected abstract List<? extends ServiceViewItem> doGetRoots();
abstract void eventProcessed(ServiceEvent e);
void filtersChanged() {
notifyListeners();
}
ServiceViewFilter getFilter() {
return myFilter;
}
@NotNull
List<? extends ServiceViewItem> 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<? extends ServiceViewItem> filterEmptyGroups(@NotNull List<? extends ServiceViewItem> items) {
return ContainerUtil.filter(items, item -> !(item instanceof ServiceGroupNode) || !getChildren(item).isEmpty());
}
@Nullable
private static ServiceViewItem findItem(ServiceViewItem viewItem, List<? extends ServiceViewItem> modelItems) {
return findItem(getPath(viewItem), modelItems);
}
@Nullable
private static ServiceViewItem findItem(Deque<ServiceViewItem> path, List<? extends ServiceViewItem> 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<ServiceViewItem> getPath(ServiceViewItem item) {
Deque<ServiceViewItem> path = new LinkedList<>();
do {
path.addFirst(item);
item = item.getParent();
}
while (item != null);
return path;
}
interface ServiceViewModelListener {
void rootsChanged();
}
static <T> List<ServiceTreeNode> getContributorChildren(Project project,
ServiceTreeNode parent,
ServiceViewContributor<T> contributor) {
Set<ServiceTreeNode> children = new LinkedHashSet<>();
Map<Object, ServiceGroupNode> groupNodes = new HashMap<>();
for (T service : contributor.getServices(project)) {
Object value = service instanceof ServiceViewProvidingContributor ? ((ServiceViewProvidingContributor)service).asService() : service;
if (contributor instanceof ServiceViewGroupingContributor) {
ServiceViewGroupingContributor<T, Object> groupingContributor = (ServiceViewGroupingContributor<T, Object>)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<ServiceViewModelListener> myListeners = ContainerUtil.newSmartList();
private final List<ServiceTreeNode> 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<? extends ServiceTreeNode> getRoots() {
if (!myRootInitialized) {
myRootInitialized = true;
myRoots.clear();
myRoots.addAll(doGetRoots());
}
return myRoots;
}
private List<? extends ServiceTreeNode> doGetRoots() {
List<ServiceTreeNode> 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<ServiceViewContributor, Integer> indexes = new HashMap<>();
List<ServiceTreeNode> 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<ServiceTreeNode> 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<? extends ServiceViewItem> 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<ServiceTreeNode> 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<ServiceTreeNode> getChildren() {
if (myChildren == null) {
myChildren = doGetChildren();
@Override
protected List<? extends ServiceViewItem> 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<ServiceTreeNode> 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<ServiceGroupNode> 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<ServiceGroupNode> 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<ServiceTreeNode> doGetChildren() {
return myProvidingContributor == null ? Collections.emptyList() : getContributorChildren(myProject, this, myProvidingContributor);
protected List<? extends ServiceViewItem> 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<ServiceViewItem> myServiceRef;
SingeServiceModel(@NotNull ServiceModel model, @NotNull ServiceModelFilter modelFilter,
@NotNull AtomicReference<ServiceViewItem> 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<ServiceTreeNode> doGetChildren() {
return new ArrayList<>();
protected List<? extends ServiceViewItem> 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<ServiceViewItem> myRoots;
ServiceListModel(@NotNull ServiceModel model, @NotNull ServiceModelFilter modelFilter, @NotNull List<ServiceViewItem> 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<? extends ServiceViewItem> doGetRoots() {
return myModelFilter.filter(myRoots, getFilter());
}
@Override
void eventProcessed(ServiceEvent e) {
boolean update = false;
List<ServiceViewItem> 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();
}
}
}
}
@@ -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);
}
}
@@ -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 {
@@ -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<Object> 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<Object> 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<Object> 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<Object> implements InvokerSuppl
Promise<TreePath> findPath(@NotNull Object service, @NotNull Class<?> contributorClass) {
AsyncPromise<TreePath> result = new AsyncPromise<>();
getInvoker().runOrInvokeLater(() -> {
ServiceTreeNode serviceNode = JBTreeTraverser.from((Function<ServiceTreeNode, List<ServiceTreeNode>>)node ->
contributorClass.isInstance(node.getContributor()) ? node.getChildren() : null)
.withRoots(myModel.getRoots())
List<? extends ServiceViewItem> roots = myModel.getRoots();
ServiceViewItem serviceNode = JBTreeTraverser.from((Function<ServiceViewItem, List<ServiceViewItem>>)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<Object> 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)));
@@ -322,7 +322,7 @@
<separator/>
<add-to-group group-id="MainMenu" anchor="after" relative-to-action="GoToMenu"/>
</group>
<group id="ParameterNameHints" popup="true">
<action id="ShowSettingsWithAddedPattern" class="com.intellij.codeInsight.hints.ShowSettingsWithAddedPattern"/>
<action id="ToggleInlineHintsAction" class="com.intellij.codeInsight.hints.ToggleInlineHintsAction"/>
@@ -330,7 +330,7 @@
<separator/>
<action id="ToggleCompletionHintsAction" class="com.intellij.codeInsight.hints.ToggleCompletionHintsAction"/>
</group>
<!-- Analyze -->
<action id="SliceBackward" class="com.intellij.slicer.SliceBackwardAction"/>
<action id="SliceForward" class="com.intellij.slicer.SliceForwardAction"/>
@@ -1179,6 +1179,8 @@
</group>
<group id="ServiceViewTreeToolbar">
<action id="ExtractServiceAction" class="com.intellij.execution.services.ExtractServiceAction"
text="Extract" icon="AllIcons.Actions.Forward"/>
<group id="ServiceViewTreeSettings" popup="true" text="Settings" icon="AllIcons.General.Settings"
class="com.intellij.ide.actions.NonEmptyActionGroup"/>
</group>