mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-15 11:53:49 +07:00
GO-18846: Display suggestions when there are no services in the Services tool window
(cherry picked from commit 33f10e2419123d8e68c937323ef7ef147f3bc4f8) IJ-CR-168429 GitOrigin-RevId: 4817144fc4c9e73e8a9efe6e44ba50fcdb751d38
This commit is contained in:
committed by
intellij-monorepo-bot
parent
55628f96a0
commit
d9735a4a45
@@ -0,0 +1,59 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.platform.execution.serviceView
|
||||
|
||||
import com.intellij.execution.ExecutionBundle
|
||||
import com.intellij.execution.services.ServiceViewEmptyTreeSuggestion
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.actionSystem.*
|
||||
import com.intellij.openapi.actionSystem.impl.ActionButtonUtil
|
||||
import com.intellij.openapi.keymap.KeymapUtil
|
||||
import com.intellij.openapi.ui.popup.JBPopupFactory
|
||||
import com.intellij.openapi.ui.popup.JBPopupListener
|
||||
import com.intellij.openapi.ui.popup.LightweightWindowEvent
|
||||
import java.awt.event.InputEvent
|
||||
import javax.swing.Icon
|
||||
|
||||
class AddServiceEmptyTreeSuggestion : ServiceViewEmptyTreeSuggestion {
|
||||
private val ADD_SERVICE_ACTION_ID: String = "ServiceView.AddService"
|
||||
|
||||
// Always the last
|
||||
override val weight: Int = -10
|
||||
|
||||
override val icon: Icon? = AllIcons.General.Add
|
||||
|
||||
override val text: String = ExecutionBundle.message("service.view.empty.tree.suggestion.add.service")
|
||||
|
||||
override val shortcutText: String?
|
||||
get() {
|
||||
val addAction = ActionManager.getInstance().getAction(ADD_SERVICE_ACTION_ID)
|
||||
val shortcutSet = addAction?.shortcutSet
|
||||
val shortcut = shortcutSet?.getShortcuts()?.firstOrNull() ?: return null
|
||||
|
||||
return KeymapUtil.getShortcutText(shortcut)
|
||||
}
|
||||
|
||||
override fun onActivate(dataContext: DataContext, inputEvent: InputEvent?) {
|
||||
val selectedView = ServiceViewActionProvider.getSelectedView(dataContext) ?: return
|
||||
val action = ActionManager.getInstance().getAction(ADD_SERVICE_ACTION_ID)
|
||||
val actionGroup = action as? ActionGroup ?: return
|
||||
|
||||
val popup = JBPopupFactory.getInstance().createActionGroupPopup(
|
||||
"",
|
||||
actionGroup,
|
||||
dataContext,
|
||||
JBPopupFactory.ActionSelectionAid.SPEEDSEARCH,
|
||||
false,
|
||||
ActionPlaces.getActionGroupPopupPlace(ADD_SERVICE_ACTION_ID))
|
||||
val button = ActionButtonUtil.findActionButtonById(selectedView, ADD_SERVICE_ACTION_ID) ?: return
|
||||
popup.addListener(object : JBPopupListener {
|
||||
override fun beforeShown(event: LightweightWindowEvent) {
|
||||
Toggleable.setSelected(button, true)
|
||||
}
|
||||
|
||||
override fun onClosed(event: LightweightWindowEvent) {
|
||||
Toggleable.setSelected(button, null)
|
||||
}
|
||||
})
|
||||
popup.showUnderneathOf(button)
|
||||
}
|
||||
}
|
||||
@@ -2,19 +2,14 @@
|
||||
package com.intellij.platform.execution.serviceView;
|
||||
|
||||
import com.intellij.execution.ExecutionBundle;
|
||||
import com.intellij.execution.services.ServiceEventListener;
|
||||
import com.intellij.execution.services.ServiceViewContributor;
|
||||
import com.intellij.execution.services.ServiceViewDescriptor;
|
||||
import com.intellij.execution.services.ServiceViewManager;
|
||||
import com.intellij.execution.services.*;
|
||||
import com.intellij.ide.DataManager;
|
||||
import com.intellij.ide.dnd.DnDManager;
|
||||
import com.intellij.ide.util.treeView.TreeState;
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.actionSystem.*;
|
||||
import com.intellij.openapi.actionSystem.DataContext;
|
||||
import com.intellij.openapi.application.AppUIExecutor;
|
||||
import com.intellij.openapi.keymap.KeymapUtil;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.popup.JBPopupFactory;
|
||||
import com.intellij.openapi.util.Comparing;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
@@ -24,7 +19,6 @@ import com.intellij.platform.execution.serviceView.ServiceViewNavBarService.Serv
|
||||
import com.intellij.platform.navbar.frontend.vm.NavBarVm;
|
||||
import com.intellij.ui.AutoScrollToSourceHandler;
|
||||
import com.intellij.ui.SimpleTextAttributes;
|
||||
import com.intellij.ui.awt.RelativePoint;
|
||||
import com.intellij.ui.tree.AsyncTreeModel;
|
||||
import com.intellij.ui.tree.RestoreSelectionListener;
|
||||
import com.intellij.ui.tree.TreeVisitor;
|
||||
@@ -47,8 +41,7 @@ import javax.swing.*;
|
||||
import javax.swing.tree.TreeModel;
|
||||
import javax.swing.tree.TreePath;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.InputEvent;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
@@ -58,8 +51,6 @@ import java.util.concurrent.CancellationException;
|
||||
import static com.intellij.platform.execution.serviceView.ServiceViewDragHelper.getTheOnlyRootContributor;
|
||||
|
||||
final class ServiceTreeView extends ServiceView {
|
||||
private static final String ADD_SERVICE_ACTION_ID = "ServiceView.AddService";
|
||||
|
||||
private final ServiceViewTree myTree;
|
||||
private final ServiceViewTreeModel myTreeModel;
|
||||
private final ServiceViewModel.ServiceViewModelListener myListener;
|
||||
@@ -459,41 +450,37 @@ final class ServiceTreeView extends ServiceView {
|
||||
}
|
||||
|
||||
private static void setEmptyText(JComponent component, StatusText emptyText) {
|
||||
emptyText.withUnscaledGapAfter(5);
|
||||
emptyText.setText(ExecutionBundle.message("service.view.empty.tree.text"));
|
||||
emptyText.appendSecondaryText(ExecutionBundle.message("service.view.add.service.action.name"),
|
||||
SimpleTextAttributes.LINK_PLAIN_ATTRIBUTES,
|
||||
new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
ActionGroup addActionGroup = ObjectUtils.tryCast(
|
||||
ActionManager.getInstance().getAction(ADD_SERVICE_ACTION_ID), ActionGroup.class);
|
||||
if (addActionGroup == null) return;
|
||||
|
||||
Point position = component.getMousePosition();
|
||||
if (position == null) {
|
||||
Rectangle componentBounds = component.getBounds();
|
||||
Rectangle textBounds = emptyText.getComponent().getBounds();
|
||||
position = new Point(componentBounds.width / 2,
|
||||
componentBounds.height / (emptyText.isShowAboveCenter() ? 3 : 2) +
|
||||
textBounds.height / 4);
|
||||
|
||||
}
|
||||
DataContext dataContext = DataManager.getInstance().getDataContext(component);
|
||||
JBPopupFactory.getInstance().createActionGroupPopup(
|
||||
addActionGroup.getTemplatePresentation().getText(), addActionGroup, dataContext,
|
||||
JBPopupFactory.ActionSelectionAid.SPEEDSEARCH,
|
||||
false, null, -1, null, ActionPlaces.getActionGroupPopupPlace(ADD_SERVICE_ACTION_ID))
|
||||
.show(new RelativePoint(component, position));
|
||||
}
|
||||
});
|
||||
AnAction addAction = ActionManager.getInstance().getAction(ADD_SERVICE_ACTION_ID);
|
||||
ShortcutSet shortcutSet = addAction == null ? null : addAction.getShortcutSet();
|
||||
Shortcut shortcut = shortcutSet == null ? null : ArrayUtil.getFirstElement(shortcutSet.getShortcuts());
|
||||
if (shortcut != null) {
|
||||
emptyText.appendSecondaryText(" (" + KeymapUtil.getShortcutText(shortcut) + ")", StatusText.DEFAULT_ATTRIBUTES, null);
|
||||
var sortedSuggestions = getEmptyTreeSuggestions();
|
||||
for (int i = 0; i < sortedSuggestions.size(); i++) {
|
||||
ServiceViewEmptyTreeSuggestion suggestion = sortedSuggestions.get(i);
|
||||
String suggestionText = suggestion.getText();
|
||||
Icon icon = suggestion.getIcon();
|
||||
emptyText.appendText(0, i + 1, icon, suggestionText, SimpleTextAttributes.LINK_PLAIN_ATTRIBUTES, e -> {
|
||||
InputEvent inputEvent = e.getSource() instanceof InputEvent ie ? ie : null;
|
||||
DataContext dataContext = DataManager.getInstance().getDataContext(component);
|
||||
suggestion.onActivate(dataContext, inputEvent);
|
||||
});
|
||||
String shortcutText = suggestion.getShortcutText();
|
||||
if (shortcutText != null) {
|
||||
String paddedText = " " + shortcutText;
|
||||
emptyText.appendText(0, i + 1, icon, paddedText, SimpleTextAttributes.GRAYED_ATTRIBUTES, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<ServiceViewEmptyTreeSuggestion> getEmptyTreeSuggestions() {
|
||||
List<ServiceViewEmptyTreeSuggestion> externalSuggestions =
|
||||
ContainerUtil.mapNotNull(ServiceViewContributor.CONTRIBUTOR_EP_NAME.getExtensionList(),
|
||||
ServiceViewContributor::getEmptyTreeSuggestion);
|
||||
var allSuggestions = ContainerUtil.append(externalSuggestions, new AddServiceEmptyTreeSuggestion());
|
||||
var highWeightFirst = Comparator.comparingInt(ServiceViewEmptyTreeSuggestion::getWeight).reversed();
|
||||
return ContainerUtil.sorted(allSuggestions, highWeightFirst);
|
||||
}
|
||||
|
||||
private static List<TreePath> adjustPaths(List<? extends TreePath> paths, Collection<? extends ServiceViewItem> roots, Object treeRoot) {
|
||||
List<TreePath> result = new SmartList<>();
|
||||
for (TreePath path : paths) {
|
||||
|
||||
@@ -370,6 +370,7 @@ run.dashboard.apply.to.all.types=<a>Apply to all types</a>
|
||||
|
||||
service.view.open.in.new.tab.ad.text=Drag node onto tool window header to open a new tab
|
||||
service.view.empty.tree.text=No services configured.
|
||||
service.view.empty.tree.suggestion.add.service=Add a service
|
||||
service.view.empty.tab.text=No content available
|
||||
service.view.empty.selection.text=Select service to view details
|
||||
service.view.add.service.action.name=Add service
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.intellij.execution.services;
|
||||
|
||||
import com.intellij.openapi.extensions.ExtensionPointName;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
@@ -37,4 +38,11 @@ public interface ServiceViewContributor<T> {
|
||||
/// @return a [ServiceViewDescriptor] for the child node [T]
|
||||
@NotNull
|
||||
ServiceViewDescriptor getServiceDescriptor(@NotNull Project project, @NotNull T service);
|
||||
|
||||
/// @see ServiceViewEmptyTreeSuggestion
|
||||
@ApiStatus.Internal
|
||||
@Nullable
|
||||
default ServiceViewEmptyTreeSuggestion getEmptyTreeSuggestion() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.execution.services
|
||||
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.annotations.Nls
|
||||
import java.awt.event.InputEvent
|
||||
import javax.swing.Icon
|
||||
|
||||
/**
|
||||
* Represents a suggestion displayed when there are no services in the Services tool window.
|
||||
* Enhances the UX for users opening the Services tool window for the first time.
|
||||
* @see ServiceViewContributor
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
interface ServiceViewEmptyTreeSuggestion {
|
||||
val weight: Int
|
||||
|
||||
val icon: Icon?
|
||||
|
||||
@get:Nls(capitalization = Nls.Capitalization.Sentence)
|
||||
val text: String
|
||||
|
||||
@get:NlsSafe
|
||||
val shortcutText: String?
|
||||
get() = null
|
||||
|
||||
fun onActivate(dataContext: DataContext, inputEvent: InputEvent?)
|
||||
}
|
||||
Reference in New Issue
Block a user