1 // Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
2 package com.intellij.execution.actions;
4 import com.intellij.execution.*;
5 import com.intellij.execution.executors.DefaultRunExecutor;
6 import com.intellij.execution.executors.ExecutorGroup;
7 import com.intellij.execution.impl.EditConfigurationsDialog;
8 import com.intellij.execution.impl.RunManagerImpl;
9 import com.intellij.icons.AllIcons;
10 import com.intellij.ide.DataManager;
11 import com.intellij.idea.ActionsBundle;
12 import com.intellij.openapi.actionSystem.*;
13 import com.intellij.openapi.actionSystem.ex.ActionUtil;
14 import com.intellij.openapi.actionSystem.ex.ComboBoxAction;
15 import com.intellij.openapi.application.ApplicationManager;
16 import com.intellij.openapi.project.DumbAware;
17 import com.intellij.openapi.project.DumbAwareAction;
18 import com.intellij.openapi.project.IndexNotReadyException;
19 import com.intellij.openapi.project.Project;
20 import com.intellij.openapi.util.NlsSafe;
21 import com.intellij.openapi.util.registry.Registry;
22 import com.intellij.ui.SizedIcon;
23 import com.intellij.ui.components.panels.NonOpaquePanel;
24 import com.intellij.ui.scale.JBUIScale;
25 import com.intellij.util.PlatformUtils;
26 import com.intellij.util.containers.ContainerUtil;
27 import com.intellij.util.ui.EmptyIcon;
28 import com.intellij.util.ui.JBUI;
29 import com.intellij.util.ui.UIUtil;
30 import org.jetbrains.annotations.ApiStatus;
31 import org.jetbrains.annotations.NotNull;
32 import org.jetbrains.annotations.Nullable;
35 import javax.swing.border.Border;
37 import java.awt.event.ActionEvent;
38 import java.awt.event.InputEvent;
39 import java.awt.event.MouseEvent;
40 import java.util.List;
42 import java.util.function.Function;
44 public class RunConfigurationsComboBoxAction extends ComboBoxAction implements DumbAware {
45 private static final String BUTTON_MODE = "ButtonMode";
47 public static final Icon CHECKED_ICON = JBUIScale.scaleIcon(new SizedIcon(AllIcons.Actions.Checked, 16, 16));
48 public static final Icon CHECKED_SELECTED_ICON = JBUIScale.scaleIcon(new SizedIcon(AllIcons.Actions.Checked_selected, 16, 16));
49 public static final Icon EMPTY_ICON = EmptyIcon.ICON_16;
51 public static boolean hasRunCurrentFileItem(@NotNull Project project) {
52 if (RunManager.getInstance(project).isRunWidgetActive()) {
53 // Run Widget shows up only in Rider. In other IDEs it's a secret feature backed by the "ide.run.widget" Registry key.
54 // The 'Run Current File' feature doesn't look great together with the Run Widget.
58 if (PlatformUtils.isIntelliJ()) return true;
59 if (PlatformUtils.isPhpStorm()) return true;
60 if (PlatformUtils.isWebStorm()) return true;
61 if (PlatformUtils.isRubyMine()) return true;
62 if (PlatformUtils.isPyCharmPro()) return true;
63 if (PlatformUtils.isPyCharmCommunity()) return true;
65 return Registry.is("run.current.file.item.in.run.configurations.combobox");
69 public @NotNull ActionUpdateThread getActionUpdateThread() {
70 return ActionUpdateThread.BGT;
74 public void update(@NotNull AnActionEvent e) {
75 Presentation presentation = e.getPresentation();
76 Project project = e.getData(CommonDataKeys.PROJECT);
77 if (ActionPlaces.isMainMenuOrActionSearch(e.getPlace())) {
78 presentation.setDescription(ExecutionBundle.messagePointer("choose.run.configuration.action.description"));
81 if (project == null || project.isDisposed() || !project.isOpen()) {
82 updatePresentation(null, null, null, presentation, e.getPlace());
83 presentation.setEnabled(false);
86 updatePresentation(getSelectedExecutionTarget(e),
87 getSelectedConfiguration(e),
91 presentation.setEnabled(true);
94 catch (IndexNotReadyException e1) {
95 presentation.setEnabled(false);
99 protected @Nullable ExecutionTarget getSelectedExecutionTarget(AnActionEvent e) {
100 Project project = e.getProject();
101 return project == null ? null : ExecutionTargetManager.getActiveTarget(project);
104 protected @Nullable RunnerAndConfigurationSettings getSelectedConfiguration(AnActionEvent e) {
105 Project project = e.getProject();
106 return project == null ? null : RunManager.getInstance(project).getSelectedConfiguration();
109 protected static void updatePresentation(@Nullable ExecutionTarget target,
110 @Nullable RunnerAndConfigurationSettings settings,
111 @Nullable Project project,
112 @NotNull Presentation presentation,
113 String actionPlace) {
114 presentation.putClientProperty(BUTTON_MODE, null);
115 if (project != null && target != null && settings != null) {
116 String name = Executor.shortenNameIfNeeded(settings.getName());
117 if (target != DefaultExecutionTarget.INSTANCE && !target.isExternallyManaged()) {
118 name += " | " + target.getDisplayName();
120 if (!ExecutionTargetManager.canRun(settings.getConfiguration(), target)) {
121 name += " | " + ExecutionBundle.message("run.configurations.combo.action.nothing.to.run.on");
124 presentation.setText(name, false);
125 if (!ApplicationManager.getApplication().isUnitTestMode()) {
126 setConfigurationIcon(presentation, settings, project);
130 if (project != null && hasRunCurrentFileItem(project)) {
131 presentation.setText(ExecutionBundle.messagePointer("run.configurations.combo.run.current.file.selected"));
132 presentation.setIcon(null);
136 presentation.putClientProperty(BUTTON_MODE, Boolean.TRUE);
137 presentation.setText(ExecutionBundle.messagePointer("action.presentation.RunConfigurationsComboBoxAction.text"));
138 presentation.setDescription(ActionsBundle.actionDescription(IdeActions.ACTION_EDIT_RUN_CONFIGURATIONS));
139 if (ActionPlaces.TOUCHBAR_GENERAL.equals(actionPlace))
140 presentation.setIcon(AllIcons.General.Add);
142 presentation.setIcon(null);
146 protected static void setConfigurationIcon(final Presentation presentation,
147 final RunnerAndConfigurationSettings settings,
148 final Project project) {
150 presentation.setIcon(RunManagerEx.getInstanceEx(project).getConfigurationIcon(settings, true));
152 catch (IndexNotReadyException ignored) {
157 public void actionPerformed(@NotNull AnActionEvent e) {
158 if (ActionPlaces.TOUCHBAR_GENERAL.equals(e.getPlace())) {
159 final Presentation presentation = e.getPresentation();
160 if (Boolean.TRUE.equals(presentation.getClientProperty(BUTTON_MODE))) {
161 InputEvent inputEvent = e.getInputEvent();
162 Component component = inputEvent != null ? inputEvent.getComponent() : null;
163 if (component != null) {
164 performWhenButton(component, ActionPlaces.TOUCHBAR_GENERAL);
169 super.actionPerformed(e);
173 protected boolean shouldShowDisabledActions() {
179 public JComponent createCustomComponent(@NotNull final Presentation presentation, @NotNull String place) {
180 ComboBoxButton button = new RunConfigurationsComboBoxButton(presentation);
181 NonOpaquePanel panel = new NonOpaquePanel(new BorderLayout());
182 Border border = UIUtil.isUnderDefaultMacTheme() ?
183 JBUI.Borders.empty(0, 2) : JBUI.Borders.empty(0, 5, 0, 4);
185 panel.setBorder(border);
190 private static void performWhenButton(@NotNull Component src, String place) {
191 ActionManager manager = ActionManager.getInstance();
192 manager.tryToExecute(manager.getAction(IdeActions.ACTION_EDIT_RUN_CONFIGURATIONS),
193 new MouseEvent(src, MouseEvent.MOUSE_PRESSED, System.currentTimeMillis(), 0, 0, 0, 0,false, 0),
199 * @deprecated It is a temporary function just to reuse existing code. Will be soon deleted.
201 @SuppressWarnings("DeprecatedIsStillUsed")
205 public DefaultActionGroup createPopupActionGroupOpen(final JComponent button) {
206 return createPopupActionGroup(button);
211 protected DefaultActionGroup createPopupActionGroup(final JComponent button) {
212 final DefaultActionGroup allActionsGroup = new DefaultActionGroup();
213 final Project project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(button));
214 if (project == null) {
215 return allActionsGroup;
218 AnAction editRunConfigurationAction = getEditRunConfigurationAction();
219 if(editRunConfigurationAction != null) {
220 allActionsGroup.add(editRunConfigurationAction);
222 allActionsGroup.add(new SaveTemporaryAction());
223 allActionsGroup.addSeparator();
225 addTargetGroup(project, allActionsGroup);
227 allActionsGroup.add(new RunCurrentFileAction(executor -> true));
228 allActionsGroup.addSeparator(ExecutionBundle.message("run.configurations.popup.existing.configurations.separator.text"));
230 for (Map<String, List<RunnerAndConfigurationSettings>> structure : RunManagerImpl.getInstanceImpl(project).getConfigurationsGroupedByTypeAndFolder(true).values()) {
231 final DefaultActionGroup actionGroup = new DefaultActionGroup();
232 for (Map.Entry<String, List<RunnerAndConfigurationSettings>> entry : structure.entrySet()) {
233 @NlsSafe String folderName = entry.getKey();
234 DefaultActionGroup group = folderName == null ? actionGroup : DefaultActionGroup.createPopupGroup(() -> folderName);
235 group.getTemplatePresentation().setIcon(AllIcons.Nodes.Folder);
236 for (RunnerAndConfigurationSettings settings : entry.getValue()) {
237 group.add(createFinalAction(settings, project));
239 if (group != actionGroup) {
240 actionGroup.add(group);
244 allActionsGroup.add(actionGroup);
245 allActionsGroup.addSeparator();
247 return allActionsGroup;
250 protected void addTargetGroup(Project project, DefaultActionGroup allActionsGroup) {
251 RunnerAndConfigurationSettings selected = RunManager.getInstance(project).getSelectedConfiguration();
252 if (selected != null) {
253 ExecutionTarget activeTarget = ExecutionTargetManager.getActiveTarget(project);
254 for (ExecutionTarget eachTarget : ExecutionTargetManager.getTargetsToChooseFor(project, selected.getConfiguration())) {
255 allActionsGroup.add(new SelectTargetAction(project, eachTarget, eachTarget.equals(activeTarget)));
257 allActionsGroup.addSeparator();
261 protected @Nullable AnAction getEditRunConfigurationAction() {
262 return ActionManager.getInstance().getAction(IdeActions.ACTION_EDIT_RUN_CONFIGURATIONS);
265 protected AnAction createFinalAction(@NotNull final RunnerAndConfigurationSettings configuration, @NotNull final Project project) {
266 return new SelectConfigAction(configuration, project, executor -> true);
269 public class RunConfigurationsComboBoxButton extends ComboBoxButton {
271 public RunConfigurationsComboBoxButton(@NotNull Presentation presentation) {
276 public Dimension getPreferredSize() {
277 Dimension d = super.getPreferredSize();
278 d.width = Math.max(d.width, JBUIScale.scale(75));
283 protected void doShiftClick() {
284 DataContext context = DataManager.getInstance().getDataContext(this);
285 final Project project = CommonDataKeys.PROJECT.getData(context);
286 if (project != null && !ActionUtil.isDumbMode(project)) {
287 new EditConfigurationsDialog(project).show();
290 super.doShiftClick();
294 protected void fireActionPerformed(ActionEvent event) {
295 if (Boolean.TRUE.equals(getPresentation().getClientProperty(BUTTON_MODE))) {
296 performWhenButton(this, ActionPlaces.UNKNOWN);
300 super.fireActionPerformed(event);
304 protected boolean isArrowVisible(@NotNull Presentation presentation) {
305 return !Boolean.TRUE.equals(presentation.getClientProperty(BUTTON_MODE));
311 private static final class SaveTemporaryAction extends DumbAwareAction {
312 SaveTemporaryAction() {
313 Presentation presentation = getTemplatePresentation();
314 presentation.setIcon(AllIcons.Actions.MenuSaveall);
318 public void actionPerformed(@NotNull final AnActionEvent e) {
319 final Project project = e.getData(CommonDataKeys.PROJECT);
320 if (project != null) {
321 RunnerAndConfigurationSettings settings = chooseTempSettings(project);
322 if (settings != null) {
323 final RunManager runManager = RunManager.getInstance(project);
324 runManager.makeStable(settings);
330 public void update(@NotNull final AnActionEvent e) {
331 final Presentation presentation = e.getPresentation();
332 final Project project = e.getData(CommonDataKeys.PROJECT);
333 if (project == null) {
334 disable(presentation);
337 RunnerAndConfigurationSettings settings = chooseTempSettings(project);
338 if (settings == null) {
339 disable(presentation);
342 presentation.setText(ExecutionBundle.messagePointer("save.temporary.run.configuration.action.name", Executor.shortenNameIfNeeded(settings.getName())));
343 //noinspection DialogTitleCapitalization
344 presentation.setDescription(presentation.getText());
345 presentation.setEnabledAndVisible(true);
349 private static void disable(final Presentation presentation) {
350 presentation.setEnabledAndVisible(false);
354 private static RunnerAndConfigurationSettings chooseTempSettings(@NotNull Project project) {
355 RunnerAndConfigurationSettings selectedConfiguration = RunManager.getInstance(project).getSelectedConfiguration();
356 if (selectedConfiguration != null && selectedConfiguration.isTemporary()) {
357 return selectedConfiguration;
359 return ContainerUtil.getFirstItem(RunManager.getInstance(project).getTempConfigurationsList());
364 private static void addExecutorActions(@NotNull DefaultActionGroup group,
365 @NotNull Function<? super Executor, ? extends ExecutorRegistryImpl.ExecutorAction> actionCreator,
366 @NotNull Function<? super Executor, Boolean> executorFilter) {
367 for (Executor executor : Executor.EXECUTOR_EXTENSION_NAME.getExtensionList()) {
368 if (executor instanceof ExecutorGroup) {
369 for (Executor childExecutor : ((ExecutorGroup<?>)executor).childExecutors()) {
370 if (executorFilter.apply(childExecutor)) {
371 group.addAction(actionCreator.apply(childExecutor));
376 if (executorFilter.apply(executor)) {
377 group.addAction(actionCreator.apply(executor));
384 public static class RunCurrentFileAction extends DefaultActionGroup implements DumbAware {
385 private final @NotNull Function<? super Executor, Boolean> myExecutorFilter;
387 public RunCurrentFileAction(@NotNull Function<? super Executor, Boolean> executorFilter) {
388 super(ExecutionBundle.messagePointer("run.configurations.combo.run.current.file.item.in.dropdown"),
389 ExecutionBundle.messagePointer("run.configurations.combo.run.current.file.description"),
391 myExecutorFilter = executorFilter;
393 getTemplatePresentation().setPerformGroup(true);
398 private void addSubActions() {
399 // Add actions similar to com.intellij.execution.actions.ChooseRunConfigurationPopup.ConfigurationActionsStep#buildActions
400 addExecutorActions(this, ExecutorRegistryImpl.RunCurrentFileExecutorAction::new, myExecutorFilter);
402 addAction(new ExecutorRegistryImpl.EditRunConfigAndRunCurrentFileExecutorAction(DefaultRunExecutor.getRunExecutorInstance()));
406 public void update(@NotNull AnActionEvent e) {
407 e.getPresentation().setEnabledAndVisible(e.getProject() != null && hasRunCurrentFileItem(e.getProject()));
411 public void actionPerformed(@NotNull AnActionEvent e) {
412 Project project = e.getProject();
413 if (project == null) return;
415 RunManager.getInstance(project).setSelectedConfiguration(null);
416 updatePresentation(null, null, project, e.getPresentation(), e.getPlace());
421 private static final class SelectTargetAction extends AnAction {
422 private final Project myProject;
423 private final ExecutionTarget myTarget;
425 SelectTargetAction(final Project project, final ExecutionTarget target, boolean selected) {
429 String name = target.getDisplayName();
430 Presentation presentation = getTemplatePresentation();
431 presentation.setText(name, false);
432 presentation.setDescription(ExecutionBundle.message("select.0", name));
434 presentation.setIcon(selected ? CHECKED_ICON : EMPTY_ICON);
435 presentation.setSelectedIcon(selected ? CHECKED_SELECTED_ICON : EMPTY_ICON);
439 public void actionPerformed(@NotNull AnActionEvent e) {
440 ExecutionTargetManager.setActiveTarget(myProject, myTarget);
441 updatePresentation(ExecutionTargetManager.getActiveTarget(myProject),
442 RunManager.getInstance(myProject).getSelectedConfiguration(),
449 public boolean isDumbAware() {
450 RunnerAndConfigurationSettings configuration = RunManager.getInstance(myProject).getSelectedConfiguration();
451 return configuration == null || configuration.getType().isDumbAware();
456 public static final class SelectConfigAction extends DefaultActionGroup implements DumbAware {
457 private final RunnerAndConfigurationSettings myConfiguration;
458 private final Project myProject;
459 private final @NotNull Function<? super Executor, Boolean> myExecutorFilter;
461 public SelectConfigAction(final RunnerAndConfigurationSettings configuration, final Project project, @NotNull Function<? super Executor, Boolean> executorFilter) {
462 myConfiguration = configuration;
464 myExecutorFilter = executorFilter;
467 getTemplatePresentation().setPerformGroup(true);
469 String name = Executor.shortenNameIfNeeded(configuration.getName());
470 if (name.isEmpty()) {
473 final Presentation presentation = getTemplatePresentation();
474 presentation.setText(name, false);
475 presentation.setDescription(ExecutionBundle.message("select.0.1", configuration.getType().getConfigurationTypeDescription(), name));
476 updateIcon(presentation);
478 // Secondary menu for the existing run configurations is not directly related to the 'Run Current File' feature.
479 // We may reconsider changing this to `if (!RunManager.getInstance(project).isRunWidgetActive()) { addSubActions(); }`
480 if (hasRunCurrentFileItem(project)) {
485 private void updateIcon(final Presentation presentation) {
486 setConfigurationIcon(presentation, myConfiguration, myProject);
489 private void addSubActions() {
490 // Add actions similar to com.intellij.execution.actions.ChooseRunConfigurationPopup.ConfigurationActionsStep#buildActions
491 addExecutorActions(this,
492 executor -> new ExecutorRegistryImpl.RunSpecifiedConfigExecutorAction(executor, myConfiguration, false),
496 Executor runExecutor = DefaultRunExecutor.getRunExecutorInstance();
497 addAction(new ExecutorRegistryImpl.RunSpecifiedConfigExecutorAction(runExecutor, myConfiguration, true));
499 if (myConfiguration.isTemporary()) {
500 String actionName = ExecutionBundle.message("choose.run.popup.save");
501 String description = ExecutionBundle.message("choose.run.popup.save.description");
502 addAction(new AnAction(actionName, description, AllIcons.Actions.MenuSaveall) {
504 public void actionPerformed(@NotNull AnActionEvent e) {
505 RunManager.getInstance(myProject).makeStable(myConfiguration);
510 String actionName = ExecutionBundle.message("choose.run.popup.delete");
511 String description = ExecutionBundle.message("choose.run.popup.delete.description");
512 addAction(new AnAction(actionName, description, AllIcons.Actions.Cancel) {
514 public void actionPerformed(@NotNull AnActionEvent e) {
515 ChooseRunConfigurationPopup.deleteConfiguration(myProject, myConfiguration, null);
521 public void actionPerformed(@NotNull final AnActionEvent e) {
522 RunManager.getInstance(myProject).setSelectedConfiguration(myConfiguration);
523 updatePresentation(ExecutionTargetManager.getActiveTarget(myProject), myConfiguration, myProject, e.getPresentation(), e.getPlace());
527 public void update(@NotNull final AnActionEvent e) {
529 updateIcon(e.getPresentation());