Run Dashboard: register tool window via extension and manage its availability.

This commit is contained in:
Konstantin Aleev
2017-02-21 20:43:08 +03:00
parent 6661579e04
commit 34c797eb6c
8 changed files with 250 additions and 127 deletions
@@ -15,12 +15,18 @@
*/
package com.intellij.execution.dashboard;
import com.intellij.execution.RunnerAndConfigurationSettings;
import com.intellij.execution.ui.RunContentDescriptor;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.ui.content.ContentManager;
import com.intellij.util.messages.Topic;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.util.List;
/**
* @author konstantin.aleev
@@ -38,4 +44,10 @@ public interface RunDashboardManager {
String getToolWindowId();
Icon getToolWindowIcon();
boolean isToolWindowAvailable();
void createToolWindowContent(@NotNull ToolWindow toolWindow);
List<Pair<RunnerAndConfigurationSettings, RunContentDescriptor>> getRunConfigurations();
}
@@ -15,11 +15,9 @@
*/
package com.intellij.execution.dashboard;
import com.intellij.execution.*;
import com.intellij.execution.ExecutionBundle;
import com.intellij.execution.dashboard.tree.DashboardGrouper;
import com.intellij.execution.dashboard.tree.RunDashboardTreeStructure;
import com.intellij.execution.process.ProcessHandler;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.ide.CommonActionsManager;
import com.intellij.ide.DataManager;
import com.intellij.ide.DefaultTreeExpander;
@@ -30,7 +28,6 @@ import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Splitter;
import com.intellij.openapi.util.Comparing;
@@ -40,10 +37,8 @@ import com.intellij.ui.components.JBPanelWithEmptyText;
import com.intellij.ui.content.*;
import com.intellij.ui.treeStructure.Tree;
import com.intellij.util.ObjectUtils;
import com.intellij.util.messages.MessageBusConnection;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.TreeExpansionEvent;
@@ -231,59 +226,6 @@ public class RunDashboardContent extends JPanel implements TreeContent, Disposab
};
myBuilder.initRootNode();
Disposer.register(this, myBuilder);
RunManagerEx.getInstanceEx(myProject).addRunManagerListener(new RunManagerListener() {
@Override
public void runConfigurationAdded(@NotNull RunnerAndConfigurationSettings settings) {
updateTreeIfNeeded(settings);
}
@Override
public void runConfigurationRemoved(@NotNull RunnerAndConfigurationSettings settings) {
updateTreeIfNeeded(settings);
}
@Override
public void runConfigurationChanged(@NotNull RunnerAndConfigurationSettings settings) {
updateTreeIfNeeded(settings);
}
});
MessageBusConnection connection = myProject.getMessageBus().connect(myProject);
connection.subscribe(ExecutionManager.EXECUTION_TOPIC, new ExecutionListener() {
@Override
public void processStarted(@NotNull String executorId, @NotNull ExecutionEnvironment env, final @NotNull ProcessHandler handler) {
updateTreeIfNeeded(env.getRunnerAndConfigurationSettings());
}
@Override
public void processTerminated(@NotNull String executorId,
@NotNull ExecutionEnvironment env,
@NotNull ProcessHandler handler,
int exitCode) {
updateTreeIfNeeded(env.getRunnerAndConfigurationSettings());
}
});
connection.subscribe(RunDashboardManager.DASHBOARD_TOPIC, new DashboardListener() {
@Override
public void contentChanged(boolean withStructure) {
updateTree(withStructure);
}
});
connection.subscribe(DumbService.DUMB_MODE, new DumbService.DumbModeListener() {
@Override
public void enteredDumbMode() {
}
@Override
public void exitDumbMode() {
updateTree(false);
}
});
}
private void updateTreeIfNeeded(@Nullable RunnerAndConfigurationSettings settings) {
if (settings != null && RunDashboardContributor.isShowInDashboard(settings.getType())) {
updateTree(true);
}
}
private JComponent createToolbar() {
@@ -330,8 +272,11 @@ public class RunDashboardContent extends JPanel implements TreeContent, Disposab
public void dispose() {
}
public void updateTree(boolean withStructure) {
public void updateContent(boolean withStructure) {
ApplicationManager.getApplication().invokeLater(() -> myBuilder.queueUpdate(withStructure).doWhenDone(() -> {
if (!withStructure) {
return;
}
// Remove nodes not presented in the tree from collapsed node values set.
// Children retrieving is quick since grouping and run configuration nodes are already constructed.
Set<Object> nodes = new HashSet<>();
@@ -378,7 +323,7 @@ public class RunDashboardContent extends JPanel implements TreeContent, Disposab
@Override
public void setSelected(AnActionEvent e, boolean state) {
myGrouper.setEnabled(state);
updateTree(true);
updateContent(true);
}
}
}
@@ -15,15 +15,23 @@
*/
package com.intellij.execution.dashboard;
import com.intellij.execution.*;
import com.intellij.execution.configurations.RunConfiguration;
import com.intellij.execution.dashboard.tree.DashboardGrouper;
import com.intellij.execution.impl.ExecutionManagerImpl;
import com.intellij.execution.process.ProcessHandler;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.execution.ui.RunContentDescriptor;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.application.ApplicationManager;
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.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowAnchor;
@@ -33,6 +41,7 @@ import com.intellij.ui.content.Content;
import com.intellij.ui.content.ContentFactory;
import com.intellij.ui.content.ContentManager;
import com.intellij.ui.content.ContentUI;
import com.intellij.util.messages.MessageBusConnection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -40,6 +49,7 @@ import javax.swing.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
@@ -50,35 +60,81 @@ import java.util.stream.Collectors;
storages = @Storage(StoragePathMacros.WORKSPACE_FILE)
)
public class RunDashboardManagerImpl implements RunDashboardManager, PersistentStateComponent<RunDashboardManagerImpl.State> {
@NotNull private final Project myProject;
@NotNull private final ContentManager myContentManager;
private List<DashboardGrouper> myGroupers = new ArrayList<>();
@NotNull private final List<DashboardGrouper> myGroupers;
private RunDashboardContent myDashboardContent;
public RunDashboardManagerImpl(@NotNull final Project project) {
myProject = project;
ContentFactory contentFactory = ContentFactory.SERVICE.getInstance();
ContentUI contentUI = new PanelContentUI();
myContentManager = contentFactory.createContentManager(contentUI, false, project);
ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(project);
ToolWindow toolWindow = toolWindowManager.registerToolWindow(getToolWindowId(), false, ToolWindowAnchor.BOTTOM,
project, true);
toolWindow.setIcon(getToolWindowIcon());
if (!ApplicationManager.getApplication().isUnitTestMode()) {
myGroupers = Arrays.stream(DashboardGroupingRule.EP_NAME.getExtensions())
.sorted(DashboardGroupingRule.PRIORITY_COMPARATOR)
.map(DashboardGrouper::new)
.collect(Collectors.toList());
myGroupers = Arrays.stream(DashboardGroupingRule.EP_NAME.getExtensions())
.sorted(DashboardGroupingRule.PRIORITY_COMPARATOR)
.map(DashboardGrouper::new)
.collect(Collectors.toList());
RunDashboardContent dashboardContent = new RunDashboardContent(project, myContentManager, myGroupers);
Content content = contentFactory.createContent(dashboardContent, null, false);
Disposer.register(content, dashboardContent);
toolWindow.getContentManager().addContent(content);
if (isDashboardEnabled()) {
initToolWindowListeners();
}
}
if (!Registry.is("ide.run.dashboard")) {
toolWindow.setAvailable(false, null);
}
private static boolean isDashboardEnabled() {
return Registry.is("ide.run.dashboard") && RunDashboardContributor.EP_NAME.getExtensions().length > 0;
}
// TODO [konstantin.aleev] control tool window availability and visibility.
private void initToolWindowListeners() {
RunManagerEx.getInstanceEx(myProject).addRunManagerListener(new RunManagerListener() {
@Override
public void runConfigurationAdded(@NotNull RunnerAndConfigurationSettings settings) {
updateDashboardIfNeeded(settings);
}
@Override
public void runConfigurationRemoved(@NotNull RunnerAndConfigurationSettings settings) {
updateDashboardIfNeeded(settings);
}
@Override
public void runConfigurationChanged(@NotNull RunnerAndConfigurationSettings settings) {
updateDashboardIfNeeded(settings);
}
});
MessageBusConnection connection = myProject.getMessageBus().connect(myProject);
connection.subscribe(ExecutionManager.EXECUTION_TOPIC, new ExecutionListener() {
@Override
public void processStarted(@NotNull String executorId, @NotNull ExecutionEnvironment env, final @NotNull ProcessHandler handler) {
updateDashboardIfNeeded(env.getRunnerAndConfigurationSettings());
}
@Override
public void processTerminated(@NotNull String executorId,
@NotNull ExecutionEnvironment env,
@NotNull ProcessHandler handler,
int exitCode) {
updateDashboardIfNeeded(env.getRunnerAndConfigurationSettings());
}
});
connection.subscribe(RunDashboardManager.DASHBOARD_TOPIC, new DashboardListener() {
@Override
public void contentChanged(boolean withStructure) {
updateDashboard(withStructure);
}
});
connection.subscribe(DumbService.DUMB_MODE, new DumbService.DumbModeListener() {
@Override
public void enteredDumbMode() {
}
@Override
public void exitDumbMode() {
updateDashboard(false);
}
});
}
@Override
@@ -96,6 +152,105 @@ public class RunDashboardManagerImpl implements RunDashboardManager, PersistentS
return AllIcons.Toolwindows.ToolWindowRun; // TODO [konstantin.aleev] provide new icon
}
@Override
public boolean isToolWindowAvailable() {
return isDashboardEnabled() && hasContent();
}
@Override
public void createToolWindowContent(@NotNull ToolWindow toolWindow) {
myDashboardContent = new RunDashboardContent(myProject, myContentManager, myGroupers);
ContentManager contentManager = toolWindow.getContentManager();
Content content = contentManager.getFactory().createContent(myDashboardContent, null, false);
Disposer.register(content, myDashboardContent);
Disposer.register(content, () -> myDashboardContent = null);
toolWindow.getContentManager().addContent(content);
}
@Override
public List<Pair<RunnerAndConfigurationSettings, RunContentDescriptor>> getRunConfigurations() {
List<Pair<RunnerAndConfigurationSettings, RunContentDescriptor>> result = new ArrayList<>();
List<RunnerAndConfigurationSettings> configurations = RunManager.getInstance(myProject).getAllSettings().stream()
.filter(runConfiguration -> RunDashboardContributor.isShowInDashboard(runConfiguration.getType()))
.collect(Collectors.toList());
//noinspection ConstantConditions ???
ExecutionManagerImpl executionManager = ExecutionManagerImpl.getInstance(myProject);
configurations.forEach(configurationSettings -> {
List<RunContentDescriptor> descriptors = executionManager.getDescriptors(
settings -> Comparing.equal(settings.getConfiguration(), configurationSettings.getConfiguration()));
if (descriptors.isEmpty()) {
result.add(Pair.create(configurationSettings, null));
} else {
descriptors.forEach(descriptor -> result.add(Pair.create(configurationSettings, descriptor)));
}
});
// It is possible that run configuration was deleted, but there is running content descriptor for such run configuration.
// It should be shown in he dashboard tree.
List<RunConfiguration> storedConfigurations = configurations.stream().map(RunnerAndConfigurationSettings::getConfiguration)
.collect(Collectors.toList());
List<RunContentDescriptor> notStoredDescriptors = executionManager.getRunningDescriptors(settings -> {
RunConfiguration configuration = settings.getConfiguration();
return RunDashboardContributor.isShowInDashboard(settings.getType()) && !storedConfigurations.contains(configuration);
});
notStoredDescriptors.forEach(descriptor -> {
Set<RunnerAndConfigurationSettings> settings = executionManager.getConfigurations(descriptor);
settings.forEach(setting -> result.add(Pair.create(setting, descriptor)));
});
return result;
}
private void updateDashboardIfNeeded(@Nullable RunnerAndConfigurationSettings settings) {
if (settings != null && RunDashboardContributor.isShowInDashboard(settings.getType())) {
updateDashboard(true);
}
}
private void updateDashboard(final boolean withStructure) {
final ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(myProject);
toolWindowManager.invokeLater(() -> {
if (myProject.isDisposed()) {
return;
}
if (withStructure) {
boolean available = hasContent();
ToolWindow toolWindow = toolWindowManager.getToolWindow(getToolWindowId());
if (toolWindow == null) {
if (available) {
createToolWindow();
}
return;
}
boolean doShow = !toolWindow.isAvailable() && available;
toolWindow.setAvailable(available, null);
if (doShow) {
toolWindow.show(null);
}
}
if (myDashboardContent != null) {
myDashboardContent.updateContent(withStructure);
}
});
}
private void createToolWindow() {
ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(myProject);
ToolWindow toolWindow = toolWindowManager.registerToolWindow(getToolWindowId(), false, ToolWindowAnchor.BOTTOM,
myProject, true);
toolWindow.setIcon(getToolWindowIcon());
createToolWindowContent(toolWindow);
}
private boolean hasContent() {
return !getRunConfigurations().isEmpty();
}
@Nullable
@Override
public State getState() {
@@ -0,0 +1,38 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.execution.dashboard;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowFactory;
import org.jetbrains.annotations.NotNull;
/**
* @author konstantin.aleev
*/
public class RunDashboardToolWindowFactory implements ToolWindowFactory, Condition<Project>, DumbAware {
@Override
public boolean value(Project project) {
return RunDashboardManager.getInstance(project).isToolWindowAvailable();
}
@Override
public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) {
RunDashboardManager.getInstance(project).createToolWindowContent(toolWindow);
}
}
@@ -43,9 +43,8 @@ import java.util.Collections;
*/
class RunConfigurationNode extends AbstractTreeNode<Pair<RunnerAndConfigurationSettings, RunContentDescriptor>>
implements DashboardRunConfigurationNode {
public RunConfigurationNode(Project project, @NotNull RunnerAndConfigurationSettings configurationSettings,
@Nullable RunContentDescriptor descriptor) {
super(project, Pair.create(configurationSettings, descriptor));
public RunConfigurationNode(Project project, @NotNull Pair<RunnerAndConfigurationSettings, RunContentDescriptor> value) {
super(project, value);
}
@Override
@@ -69,20 +68,24 @@ class RunConfigurationNode extends AbstractTreeNode<Pair<RunnerAndConfiguration
presentation.addText(configurationSettings.getName(),
isStored ? SimpleTextAttributes.REGULAR_ATTRIBUTES : SimpleTextAttributes.GRAY_ATTRIBUTES);
RunDashboardContributor contributor = RunDashboardContributor.getContributor(configurationSettings.getType());
assert contributor != null;
Icon icon = null;
DashboardRunConfigurationStatus status = contributor.getStatus(this);
if (DashboardRunConfigurationStatus.STARTED.equals(status)) {
icon = getExecutorIcon();
} else if (DashboardRunConfigurationStatus.FAILED.equals(status)) {
icon = status.getIcon();
if (contributor != null) {
DashboardRunConfigurationStatus status = contributor.getStatus(this);
if (DashboardRunConfigurationStatus.STARTED.equals(status)) {
icon = getExecutorIcon();
}
else if (DashboardRunConfigurationStatus.FAILED.equals(status)) {
icon = status.getIcon();
}
}
if (icon == null) {
icon = RunManagerEx.getInstanceEx(getProject()).getConfigurationIcon(configurationSettings);
}
presentation.setIcon(isStored ? icon : IconLoader.getDisabledIcon(icon));
contributor.updatePresentation(presentation, this);
if (contributor != null) {
contributor.updatePresentation(presentation, this);
}
}
@NotNull
@@ -15,20 +15,14 @@
*/
package com.intellij.execution.dashboard.tree;
import com.intellij.execution.RunManager;
import com.intellij.execution.RunnerAndConfigurationSettings;
import com.intellij.execution.configurations.RunConfiguration;
import com.intellij.execution.dashboard.DashboardGroup;
import com.intellij.execution.dashboard.DashboardGroupingRule;
import com.intellij.execution.dashboard.RunDashboardContributor;
import com.intellij.execution.impl.ExecutionManagerImpl;
import com.intellij.execution.ui.RunContentDescriptor;
import com.intellij.execution.dashboard.RunDashboardManager;
import com.intellij.ide.projectView.PresentationData;
import com.intellij.ide.projectView.TreeStructureProvider;
import com.intellij.ide.util.treeView.AbstractTreeNode;
import com.intellij.ide.util.treeView.AbstractTreeStructureBase;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -78,36 +72,8 @@ public class RunDashboardTreeStructure extends AbstractTreeStructureBase {
@NotNull
@Override
public Collection<? extends AbstractTreeNode> getChildren() {
List<RunConfigurationNode> nodes = new ArrayList<>();
List<RunnerAndConfigurationSettings> configurations = RunManager.getInstance(myProject).getAllSettings().stream()
.filter(runConfiguration -> RunDashboardContributor.isShowInDashboard(runConfiguration.getType()))
.collect(Collectors.toList());
//noinspection ConstantConditions ???
ExecutionManagerImpl executionManager = ExecutionManagerImpl.getInstance(myProject);
configurations.forEach(configurationSettings -> {
List<RunContentDescriptor> descriptors = executionManager.getDescriptors(settings ->
Comparing.equal(settings.getConfiguration(), configurationSettings.getConfiguration()));
if (descriptors.isEmpty()) {
nodes.add(new RunConfigurationNode(myProject, configurationSettings, null));
} else {
descriptors.forEach(descriptor -> nodes.add(new RunConfigurationNode(myProject, configurationSettings, descriptor)));
}
});
// It is possible that run configuration was deleted, but there is running content descriptor for such run configuration.
// It should be shown in he dashboard tree.
List<RunConfiguration> storedConfigurations = configurations.stream().map(RunnerAndConfigurationSettings::getConfiguration)
.collect(Collectors.toList());
List<RunContentDescriptor> notStoredDescriptors = executionManager.getRunningDescriptors(settings -> {
RunConfiguration configuration = settings.getConfiguration();
return RunDashboardContributor.isShowInDashboard(settings.getType()) && !storedConfigurations.contains(configuration);
});
notStoredDescriptors.forEach(descriptor -> {
Set<RunnerAndConfigurationSettings> settings = executionManager.getConfigurations(descriptor);
settings.forEach(setting -> nodes.add(new RunConfigurationNode(myProject, setting, descriptor)));
});
List<RunConfigurationNode> nodes = RunDashboardManager.getInstance(myProject).getRunConfigurations().stream()
.map(value -> new RunConfigurationNode(myProject, value)).collect(Collectors.toList());
return group(myProject,
this,
@@ -792,6 +792,9 @@
factoryClass="com.intellij.ide.structureView.impl.StructureViewToolWindowFactory"/>
<toolWindow id="Favorites" anchor="left" icon="AllIcons.Toolwindows.ToolWindowFavorites" secondary="true"
factoryClass="com.intellij.ide.projectView.impl.FavoritesViewToolWindowFactory"/>
<toolWindow id="Run Dashboard" anchor="bottom" icon="AllIcons.Toolwindows.ToolWindowRun"
factoryClass="com.intellij.execution.dashboard.RunDashboardToolWindowFactory"
conditionClass="com.intellij.execution.dashboard.RunDashboardToolWindowFactory" />
<psi.fileReferenceHelper implementation="com.intellij.psi.impl.source.resolve.reference.impl.providers.PsiFileReferenceHelper"/>
<psi.fileReferenceHelper implementation="com.intellij.psi.impl.source.resolve.reference.impl.providers.JarFileReferenceHelper"/>
@@ -935,6 +935,7 @@ select.run.configuration.from.context.description=Select run configuration when
ide.run.dashboard=false
ide.run.dashboard.description=Enable Run Dashboard tool window
ide.run.dashboard.restartRequired=true
ide.run.dashboard.spring.boot.jmx=true
ide.run.dashboard.spring.boot.jmx.description=Enable JMX Remote with disabled security for Spring Boot applications to access runtime information