RIDER-77734 Compound run configs are duplicated if it's started out of the toolbar
[idea/community.git] / platform / execution-impl / src / com / intellij / execution / ExecutorRegistryImpl.java
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;
3
4 import com.intellij.execution.actions.*;
5 import com.intellij.execution.compound.CompoundRunConfiguration;
6 import com.intellij.execution.compound.SettingsAndEffectiveTarget;
7 import com.intellij.execution.configurations.RunConfiguration;
8 import com.intellij.execution.executors.DefaultRunExecutor;
9 import com.intellij.execution.executors.ExecutorGroup;
10 import com.intellij.execution.impl.ExecutionManagerImpl;
11 import com.intellij.execution.impl.ExecutionManagerImplKt;
12 import com.intellij.execution.impl.RunnerAndConfigurationSettingsImpl;
13 import com.intellij.execution.runToolbar.*;
14 import com.intellij.execution.runners.ExecutionEnvironment;
15 import com.intellij.execution.runners.ExecutionEnvironmentBuilder;
16 import com.intellij.execution.runners.ExecutionUtil;
17 import com.intellij.execution.runners.ProgramRunner;
18 import com.intellij.execution.ui.RunContentDescriptor;
19 import com.intellij.icons.AllIcons;
20 import com.intellij.ide.macro.MacroManager;
21 import com.intellij.openapi.actionSystem.*;
22 import com.intellij.openapi.actionSystem.impl.ActionConfigurationCustomizer;
23 import com.intellij.openapi.diagnostic.Logger;
24 import com.intellij.openapi.editor.Editor;
25 import com.intellij.openapi.extensions.ExtensionPointListener;
26 import com.intellij.openapi.extensions.PluginDescriptor;
27 import com.intellij.openapi.fileEditor.FileEditorManager;
28 import com.intellij.openapi.project.DumbAware;
29 import com.intellij.openapi.project.DumbService;
30 import com.intellij.openapi.project.Project;
31 import com.intellij.openapi.ui.popup.IPopupChooserBuilder;
32 import com.intellij.openapi.ui.popup.JBPopupFactory;
33 import com.intellij.openapi.util.IconLoader;
34 import com.intellij.openapi.util.Key;
35 import com.intellij.openapi.util.registry.Registry;
36 import com.intellij.openapi.vfs.VirtualFile;
37 import com.intellij.openapi.wm.ToolWindowId;
38 import com.intellij.psi.PsiDocumentManager;
39 import com.intellij.psi.PsiFile;
40 import com.intellij.psi.PsiManager;
41 import com.intellij.psi.util.PsiModificationTracker;
42 import com.intellij.util.ArrayUtil;
43 import com.intellij.util.IconUtil;
44 import com.intellij.util.containers.ContainerUtil;
45 import org.jetbrains.annotations.*;
46
47 import javax.swing.*;
48 import java.awt.*;
49 import java.awt.event.InputEvent;
50 import java.awt.event.MouseEvent;
51 import java.util.List;
52 import java.util.*;
53 import java.util.function.Consumer;
54 import java.util.function.Function;
55
56 public final class ExecutorRegistryImpl extends ExecutorRegistry {
57   private static final Logger LOG = Logger.getInstance(ExecutorRegistryImpl.class);
58
59   public static final String RUNNERS_GROUP = "RunnerActions";
60   public static final String RUN_CONTEXT_GROUP = "RunContextGroupInner";
61   public static final String RUN_CONTEXT_GROUP_MORE = "RunContextGroupMore";
62
63   private final Set<String> myContextActionIdSet = new HashSet<>();
64   private final Map<String, AnAction> myIdToAction = new HashMap<>();
65   private final Map<String, AnAction> myContextActionIdToAction = new HashMap<>();
66
67   private final Map<String, AnAction> myRunWidgetIdToAction = new HashMap<>();
68
69   public ExecutorRegistryImpl() {
70     Executor.EXECUTOR_EXTENSION_NAME.addExtensionPointListener(new ExtensionPointListener<>() {
71       @Override
72       public void extensionAdded(@NotNull Executor extension, @NotNull PluginDescriptor pluginDescriptor) {
73         //noinspection TestOnlyProblems
74         initExecutorActions(extension, ActionManager.getInstance());
75       }
76
77       @Override
78       public void extensionRemoved(@NotNull Executor extension, @NotNull PluginDescriptor pluginDescriptor) {
79         deinitExecutor(extension);
80       }
81     }, null);
82   }
83
84   final static class ExecutorRegistryActionConfigurationTuner implements ActionConfigurationCustomizer {
85     @Override
86     public void customize(@NotNull ActionManager manager) {
87       if (Executor.EXECUTOR_EXTENSION_NAME.hasAnyExtensions()) {
88         ((ExecutorRegistryImpl)getInstance()).init(manager);
89       }
90     }
91   }
92
93   @TestOnly
94   public synchronized void initExecutorActions(@NotNull Executor executor, @NotNull ActionManager actionManager) {
95     if (myContextActionIdSet.contains(executor.getContextActionId())) {
96       LOG.error("Executor with context action id: \"" + executor.getContextActionId() + "\" was already registered!");
97     }
98
99     AnAction toolbarAction;
100     AnAction runContextAction;
101     AnAction runNonExistingContextAction;
102     if (executor instanceof ExecutorGroup) {
103       ExecutorGroup<?> executorGroup = (ExecutorGroup<?>)executor;
104       ActionGroup toolbarActionGroup = new SplitButtonAction(new ExecutorGroupActionGroup(executorGroup, ExecutorAction::new));
105       Presentation presentation = toolbarActionGroup.getTemplatePresentation();
106       presentation.setIcon(executor.getIcon());
107       presentation.setText(executor.getStartActionText());
108       presentation.setDescription(executor.getDescription());
109       toolbarAction = toolbarActionGroup;
110       runContextAction = new ExecutorGroupActionGroup(executorGroup, RunContextAction::new);
111       runNonExistingContextAction = new ExecutorGroupActionGroup(executorGroup, RunNewConfigurationContextAction::new);
112     }
113     else {
114       toolbarAction = new ExecutorAction(executor);
115       runContextAction = new RunContextAction(executor);
116       runNonExistingContextAction = new RunNewConfigurationContextAction(executor);
117     }
118
119     Executor.ActionWrapper customizer = executor.runnerActionsGroupExecutorActionCustomizer();
120     registerActionInGroup(actionManager, executor.getId(), customizer == null ? toolbarAction : customizer.wrap(toolbarAction), RUNNERS_GROUP, myIdToAction);
121
122     AnAction action = registerAction(actionManager, executor.getContextActionId(), runContextAction, myContextActionIdToAction);
123     if (isExecutorInMainGroup(executor)) {
124       ((DefaultActionGroup)actionManager.getAction(RUN_CONTEXT_GROUP))
125         .add(action, new Constraints(Anchor.BEFORE, RUN_CONTEXT_GROUP_MORE), actionManager);
126     }
127     else {
128       ((DefaultActionGroup)actionManager.getAction(RUN_CONTEXT_GROUP_MORE))
129         .add(action, new Constraints(Anchor.BEFORE, "CreateRunConfiguration"), actionManager);
130     }
131
132     AnAction nonExistingAction = registerAction(actionManager, newConfigurationContextActionId(executor), runNonExistingContextAction, myContextActionIdToAction);
133     ((DefaultActionGroup)actionManager.getAction(RUN_CONTEXT_GROUP_MORE))
134       .add(nonExistingAction, new Constraints(Anchor.BEFORE, "CreateNewRunConfiguration"), actionManager);
135
136     initRunToolbarExecutorActions(executor, actionManager);
137
138     myContextActionIdSet.add(executor.getContextActionId());
139   }
140
141   private synchronized void initRunToolbarExecutorActions(@NotNull Executor executor, @NotNull ActionManager actionManager) {
142     if (RunToolbarProcess.isAvailable()) {
143       RunToolbarProcess.getProcessesByExecutorId(executor.getId()).forEach(process -> {
144         if (executor instanceof ExecutorGroup) {
145
146           ExecutorGroup<?> executorGroup = (ExecutorGroup<?>)executor;
147           if (process.getShowInBar()) {
148             ActionGroup wrappedAction = new RunToolbarExecutorGroupAction(
149               new RunToolbarExecutorGroup(executorGroup, (ex) -> new RunToolbarGroupProcessAction(process, ex), process));
150             Presentation presentation = wrappedAction.getTemplatePresentation();
151             presentation.setIcon(executor.getIcon());
152             presentation.setText(process.getName());
153             presentation.setDescription(executor.getDescription());
154
155             registerActionInGroup(actionManager, process.getActionId(), wrappedAction, RunToolbarProcess.RUN_WIDGET_GROUP,
156                                   myRunWidgetIdToAction);
157           }
158           else {
159             RunToolbarAdditionActionsHolder holder = new RunToolbarAdditionActionsHolder(executorGroup, process);
160
161             registerActionInGroup(actionManager, RunToolbarAdditionActionsHolder.getAdditionActionId(process), holder.getAdditionAction(),
162                                   process.getMoreActionSubGroupName(),
163                                   myRunWidgetIdToAction);
164             registerActionInGroup(actionManager, RunToolbarAdditionActionsHolder.getAdditionActionChooserGroupId(process),
165                                   holder.getMoreActionChooserGroup(), process.getMoreActionSubGroupName(),
166                                   myRunWidgetIdToAction);
167           }
168         }
169         else {
170           if (!process.isTemporaryProcess() && process.getShowInBar()) {
171             ExecutorAction wrappedAction = new RunToolbarProcessAction(process, executor);
172             ExecutorAction wrappedMainAction = new RunToolbarProcessMainAction(process, executor);
173
174             registerActionInGroup(actionManager, process.getActionId(), wrappedAction, RunToolbarProcess.RUN_WIDGET_GROUP,
175                                   myRunWidgetIdToAction);
176
177             registerActionInGroup(actionManager, process.getMainActionId(), wrappedMainAction, RunToolbarProcess.RUN_WIDGET_MAIN_GROUP,
178                                   myRunWidgetIdToAction);
179           }
180         }
181       });
182     }
183   }
184
185   @NonNls
186   private static String newConfigurationContextActionId(@NotNull Executor executor) {
187     return "newConfiguration" + executor.getContextActionId();
188   }
189
190   private static boolean isExecutorInMainGroup(@NotNull Executor executor) {
191     return !Registry.is("executor.actions.submenu") || executor.getId().equals(ToolWindowId.RUN) || executor.getId().equals(ToolWindowId.DEBUG);
192   }
193
194   private static void registerActionInGroup(@NotNull ActionManager actionManager, @NotNull String actionId, @NotNull AnAction anAction, @NotNull String groupId, @NotNull Map<String, AnAction> map) {
195     AnAction action = registerAction(actionManager, actionId, anAction, map);
196     ((DefaultActionGroup)actionManager.getAction(groupId)).add(action, actionManager);
197   }
198
199   @NotNull
200   private static AnAction registerAction(@NotNull ActionManager actionManager,
201                                          @NotNull String actionId,
202                                          @NotNull AnAction anAction,
203                                          @NotNull Map<String, AnAction> map) {
204     AnAction action = actionManager.getAction(actionId);
205     if (action == null) {
206       actionManager.registerAction(actionId, anAction);
207       map.put(actionId, anAction);
208       action = anAction;
209     }
210     return action;
211   }
212
213   synchronized void deinitExecutor(@NotNull Executor executor) {
214     myContextActionIdSet.remove(executor.getContextActionId());
215
216     unregisterAction(executor.getId(), RUNNERS_GROUP, myIdToAction);
217     if (isExecutorInMainGroup(executor)) {
218       unregisterAction(executor.getContextActionId(), RUN_CONTEXT_GROUP, myContextActionIdToAction);
219     }
220     else {
221       unregisterAction(executor.getContextActionId(), RUN_CONTEXT_GROUP_MORE, myContextActionIdToAction);
222     }
223     unregisterAction(newConfigurationContextActionId(executor), RUN_CONTEXT_GROUP_MORE, myContextActionIdToAction);
224
225     RunToolbarProcess.getProcessesByExecutorId(executor.getId()).forEach(process -> {
226       unregisterAction(process.getActionId(), RunToolbarProcess.RUN_WIDGET_GROUP, myRunWidgetIdToAction);
227       unregisterAction(process.getMainActionId(), RunToolbarProcess.RUN_WIDGET_MAIN_GROUP, myRunWidgetIdToAction);
228
229       if (executor instanceof ExecutorGroup) {
230         unregisterAction(RunToolbarAdditionActionsHolder.getAdditionActionId(process), process.getMoreActionSubGroupName(),
231                          myRunWidgetIdToAction);
232         unregisterAction(RunToolbarAdditionActionsHolder.getAdditionActionChooserGroupId(process), process.getMoreActionSubGroupName(),
233                          myRunWidgetIdToAction);
234       }
235     });
236   }
237
238   private static void unregisterAction(@NotNull String actionId, @NotNull String groupId, @NotNull Map<String, AnAction> map) {
239     ActionManager actionManager = ActionManager.getInstance();
240     DefaultActionGroup group = (DefaultActionGroup)actionManager.getAction(groupId);
241     if (group == null) {
242       return;
243     }
244
245     AnAction action = map.get(actionId);
246     if (action != null) {
247       group.remove(action, actionManager);
248       actionManager.unregisterAction(actionId);
249       map.remove(actionId);
250     }
251     else {
252       action = ActionManager.getInstance().getAction(actionId);
253       if (action != null) {
254         group.remove(action, actionManager);
255       }
256     }
257   }
258
259   @Override
260   public Executor getExecutorById(@NotNull String executorId) {
261     // even IJ Ultimate with all plugins has ~7 executors - linear search is ok here
262     for (Executor executor : Executor.EXECUTOR_EXTENSION_NAME.getExtensionList()) {
263       if (executorId.equals(executor.getId())) {
264         return executor;
265       }
266     }
267     return null;
268   }
269
270   private void init(@NotNull ActionManager actionManager) {
271     for (Executor executor : Executor.EXECUTOR_EXTENSION_NAME.getExtensionList()) {
272       try {
273         //noinspection TestOnlyProblems
274         initExecutorActions(executor, actionManager);
275       }
276       catch (Throwable t) {
277         LOG.error("executor initialization failed: " + executor.getClass().getName(), t);
278       }
279     }
280   }
281
282   public static class ExecutorAction extends AnAction implements DumbAware, UpdateInBackground {
283     private static final Key<RunCurrentFileInfo> CURRENT_FILE_RUN_CONFIGS_KEY = Key.create("CURRENT_FILE_RUN_CONFIGS");
284
285     protected final Executor myExecutor;
286
287     protected ExecutorAction(@NotNull Executor executor) {
288       super(executor.getStartActionText(), executor.getDescription(), IconLoader.createLazy(() -> executor.getIcon()));
289       myExecutor = executor;
290     }
291
292     private boolean canRun(@NotNull Project project, @NotNull List<SettingsAndEffectiveTarget> pairs) {
293       return RunnerHelper.canRun(project, pairs, myExecutor);
294     }
295
296     @Override
297     public void update(@NotNull AnActionEvent e) {
298       Presentation presentation = e.getPresentation();
299       Project project = e.getProject();
300       if (project == null || !project.isInitialized() || project.isDisposed()) {
301         presentation.setEnabled(false);
302         return;
303       }
304
305       RunnerAndConfigurationSettings selectedSettings = getSelectedConfiguration(e);
306       boolean enabled = false;
307       boolean hideDisabledExecutorButtons = false;
308       String text;
309       if (selectedSettings != null) {
310         if (DumbService.isDumb(project) && !selectedSettings.getType().isDumbAware()) {
311           presentation.setEnabled(false);
312           return;
313         }
314
315         presentation.setIcon(getInformativeIcon(project, selectedSettings));
316         RunConfiguration configuration = selectedSettings.getConfiguration();
317         if (!isSuppressed(project)) {
318           if (configuration instanceof CompoundRunConfiguration) {
319             enabled = canRun(project, ((CompoundRunConfiguration)configuration).getConfigurationsWithEffectiveRunTargets());
320           }
321           else {
322             ExecutionTarget target = ExecutionTargetManager.getActiveTarget(project);
323             enabled = canRun(project, Collections.singletonList(new SettingsAndEffectiveTarget(configuration, target)));
324           }
325         }
326         if (!(configuration instanceof CompoundRunConfiguration)) {
327           hideDisabledExecutorButtons = configuration.hideDisabledExecutorButtons();
328         }
329         if (enabled) {
330           presentation.setDescription(myExecutor.getDescription());
331         }
332         text = myExecutor.getStartActionText(configuration.getName());
333       }
334       else {
335         if (RunConfigurationsComboBoxAction.hasRunCurrentFileItem(project)) {
336           RunCurrentFileActionStatus status = getRunCurrentFileActionStatus(e, false);
337           enabled = status.myEnabled;
338           text = status.myTooltip;
339           presentation.setIcon(status.myIcon);
340         }
341         else {
342           text = getTemplatePresentation().getTextWithMnemonic();
343         }
344       }
345
346       if (hideDisabledExecutorButtons) {
347         presentation.setEnabledAndVisible(enabled);
348       }
349       else {
350         presentation.setEnabled(enabled);
351       }
352
353       if (presentation.isVisible()) {
354         presentation.setVisible(myExecutor.isApplicable(project));
355       }
356       presentation.setText(text);
357     }
358
359     private @NotNull RunCurrentFileActionStatus getRunCurrentFileActionStatus(@NotNull AnActionEvent e, boolean resetCache) {
360       Project project = Objects.requireNonNull(e.getProject());
361       if (DumbService.isDumb(project)) {
362         return RunCurrentFileActionStatus.createDisabled(myExecutor.getStartActionText(), myExecutor.getIcon());
363       }
364
365       VirtualFile[] files = FileEditorManager.getInstance(project).getSelectedFiles();
366       if (files.length == 1) {
367         // There's only one visible editor, let's use the file from this editor, even if the editor is not in focus.
368         PsiFile psiFile = PsiManager.getInstance(project).findFile(files[0]);
369         if (psiFile == null) {
370           String tooltip = ExecutionBundle.message("run.button.on.toolbar.tooltip.current.file.not.runnable");
371           return RunCurrentFileActionStatus.createDisabled(tooltip, myExecutor.getIcon());
372         }
373
374         return getRunCurrentFileActionStatus(psiFile, resetCache);
375       }
376
377       Editor editor = e.getData(CommonDataKeys.EDITOR);
378       if (editor == null) {
379         String tooltip = ExecutionBundle.message("run.button.on.toolbar.tooltip.current.file.no.focused.editor");
380         return RunCurrentFileActionStatus.createDisabled(tooltip, myExecutor.getIcon());
381       }
382
383       PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
384       VirtualFile vFile = psiFile != null ? psiFile.getVirtualFile() : null;
385       if (psiFile == null || vFile == null || !ArrayUtil.contains(vFile, files)) {
386         // This is probably a special editor, like Python Console, which we don't want to use for the 'Run Current File' feature.
387         String tooltip = ExecutionBundle.message("run.button.on.toolbar.tooltip.current.file.no.focused.editor");
388         return RunCurrentFileActionStatus.createDisabled(tooltip, myExecutor.getIcon());
389       }
390
391       return getRunCurrentFileActionStatus(psiFile, resetCache);
392     }
393
394     private @NotNull RunCurrentFileActionStatus getRunCurrentFileActionStatus(@NotNull PsiFile psiFile, boolean resetCache) {
395       List<RunnerAndConfigurationSettings> runConfigs = getRunConfigsForCurrentFile(psiFile, resetCache);
396       if (runConfigs.isEmpty()) {
397         String tooltip = ExecutionBundle.message("run.button.on.toolbar.tooltip.current.file.not.runnable");
398         return RunCurrentFileActionStatus.createDisabled(tooltip, myExecutor.getIcon());
399       }
400
401       List<RunnerAndConfigurationSettings> runnableConfigs = filterConfigsThatHaveRunner(runConfigs);
402       if (runnableConfigs.isEmpty()) {
403         return RunCurrentFileActionStatus.createDisabled(myExecutor.getStartActionText(psiFile.getName()), myExecutor.getIcon());
404       }
405
406       Icon icon = myExecutor.getIcon();
407       if (runnableConfigs.size() == 1) {
408         icon = getInformativeIcon(psiFile.getProject(), runnableConfigs.get(0));
409       }
410       else {
411         // myExecutor.getIcon() is the least preferred icon
412         // AllIcons.Actions.Restart is more preferred
413         // Other icons are the most preferred ones (like ExecutionUtil.getLiveIndicator())
414         for (RunnerAndConfigurationSettings config : runnableConfigs) {
415           Icon anotherIcon = getInformativeIcon(psiFile.getProject(), config);
416           if (icon == myExecutor.getIcon() || (anotherIcon != myExecutor.getIcon() && anotherIcon != AllIcons.Actions.Restart)) {
417             icon = anotherIcon;
418           }
419         }
420       }
421
422       return RunCurrentFileActionStatus.createEnabled(myExecutor.getStartActionText(psiFile.getName()), icon, runnableConfigs);
423     }
424
425     private static List<RunnerAndConfigurationSettings> getRunConfigsForCurrentFile(@NotNull PsiFile psiFile, boolean resetCache) {
426       if (resetCache) {
427         psiFile.putUserData(CURRENT_FILE_RUN_CONFIGS_KEY, null);
428       }
429
430       // Without this cache, an expensive method `ConfigurationContext.getConfigurationsFromContext()` is called too often for 2 reasons:
431       // - there are several buttons on the toolbar (Run, Debug, Profile, etc.), each runs ExecutorAction.update() during each action update session
432       // - the state of the buttons on the toolbar is updated several times a second, even if no files are being edited
433
434       // The following few lines do pretty much the same as CachedValuesManager.getCachedValue(), but it's implemented without calling that
435       // method because it appeared to be too hard to satisfy both IdempotenceChecker.checkEquivalence() and CachedValueStabilityChecker.checkProvidersEquivalent().
436       // The reason is that RunnerAndConfigurationSettings class doesn't implement equals(), and that CachedValueProvider would need to capture
437       // ConfigurationContext, which doesn't implement equals() either.
438       // Effectively, we need only one boolean value: whether the action is enabled or not, so it shouldn't be a problem that
439       // RunnerAndConfigurationSettings and ConfigurationContext don't implement equals() and this code doesn't pass CachedValuesManager checks.
440
441       long psiModCount = PsiModificationTracker.getInstance(psiFile.getProject()).getModificationCount();
442       RunCurrentFileInfo cache = psiFile.getUserData(CURRENT_FILE_RUN_CONFIGS_KEY);
443
444       if (cache == null || cache.myPsiModCount != psiModCount) {
445         // The 'Run current file' feature doesn't depend on the caret position in the file, that's why ConfigurationContext is created like this.
446         ConfigurationContext configurationContext = new ConfigurationContext(psiFile);
447
448         // The 'Run current file' feature doesn't reuse existing run configurations (by design).
449         List<ConfigurationFromContext> configurationsFromContext = configurationContext.createConfigurationsFromContext();
450
451         List<RunnerAndConfigurationSettings> runConfigs =
452           configurationsFromContext != null
453           ? ContainerUtil.map(configurationsFromContext, ConfigurationFromContext::getConfigurationSettings)
454           : Collections.emptyList();
455
456         VirtualFile vFile = psiFile.getVirtualFile();
457         String filePath = vFile != null ? vFile.getPath() : null;
458         for (RunnerAndConfigurationSettings config : runConfigs) {
459           ((RunnerAndConfigurationSettingsImpl)config).setFilePathIfRunningCurrentFile(filePath);
460         }
461
462         cache = new RunCurrentFileInfo(psiModCount, runConfigs);
463         psiFile.putUserData(CURRENT_FILE_RUN_CONFIGS_KEY, cache);
464       }
465
466       return cache.myRunConfigs;
467     }
468
469     private @NotNull List<RunnerAndConfigurationSettings> filterConfigsThatHaveRunner(@NotNull List<RunnerAndConfigurationSettings> runConfigs) {
470       return ContainerUtil.filter(runConfigs, config -> ProgramRunner.getRunner(myExecutor.getId(), config.getConfiguration()) != null);
471     }
472
473     private static boolean isSuppressed(Project project) {
474       for (ExecutionActionSuppressor suppressor : ExecutionActionSuppressor.EP_NAME.getExtensionList()) {
475         if (suppressor.isSuppressed(project)) return true;
476       }
477       return false;
478     }
479
480     protected Icon getInformativeIcon(@NotNull Project project, @NotNull RunnerAndConfigurationSettings selectedConfiguration) {
481       ExecutionManagerImpl executionManager = ExecutionManagerImpl.getInstance(project);
482       RunConfiguration configuration = selectedConfiguration.getConfiguration();
483       if (configuration instanceof RunnerIconProvider) {
484         RunnerIconProvider provider = (RunnerIconProvider)configuration;
485         Icon icon = provider.getExecutorIcon(configuration, myExecutor);
486         if (icon != null) {
487           return icon;
488         }
489       }
490
491       List<RunContentDescriptor> runningDescriptors =
492         executionManager.getRunningDescriptors(s -> ExecutionManagerImplKt.isOfSameType(s, selectedConfiguration));
493       runningDescriptors = ContainerUtil.filter(runningDescriptors, descriptor -> executionManager.getExecutors(descriptor).contains(myExecutor));
494
495       if (!configuration.isAllowRunningInParallel() && !runningDescriptors.isEmpty() && DefaultRunExecutor.EXECUTOR_ID.equals(myExecutor.getId())) {
496         return AllIcons.Actions.Restart;
497       }
498       if (runningDescriptors.isEmpty()) {
499         return myExecutor.getIcon();
500       }
501
502       if (runningDescriptors.size() == 1) {
503         return ExecutionUtil.getLiveIndicator(myExecutor.getIcon());
504       }
505       else {
506         return IconUtil.addText(myExecutor.getIcon(), Integer.toString(runningDescriptors.size()));
507       }
508     }
509
510     @Nullable
511     protected RunnerAndConfigurationSettings getSelectedConfiguration(@NotNull AnActionEvent e) {
512       if(e.getProject() == null ) return null;
513       return RunManager.getInstance(e.getProject()).getSelectedConfiguration();
514     }
515
516     private void run(@NotNull Project project, @Nullable RunConfiguration configuration, @Nullable RunnerAndConfigurationSettings settings, @NotNull DataContext dataContext) {
517       RunnerHelper.run(project, configuration, settings, dataContext, myExecutor);
518     }
519
520     @Override
521     public void actionPerformed(@NotNull AnActionEvent e) {
522       final Project project = e.getProject();
523       if (project == null || project.isDisposed()) {
524         return;
525       }
526
527       MacroManager.getInstance().cacheMacrosPreview(e.getDataContext());
528       RunnerAndConfigurationSettings selectedConfiguration = getSelectedConfiguration(e);
529       if (selectedConfiguration != null) {
530         run(project, selectedConfiguration.getConfiguration(), selectedConfiguration, e.getDataContext());
531       }
532       else if (RunConfigurationsComboBoxAction.hasRunCurrentFileItem(project)) {
533         runCurrentFile(e);
534       }
535     }
536
537     private void runCurrentFile(@NotNull AnActionEvent e) {
538       List<RunnerAndConfigurationSettings> runConfigs = getRunCurrentFileActionStatus(e, true).myRunConfigs;
539       if (runConfigs.isEmpty()) {
540         return;
541       }
542
543       if (runConfigs.size() == 1) {
544         ExecutionUtil.doRunConfiguration(runConfigs.get(0), myExecutor, null, null, e.getDataContext());
545         return;
546       }
547
548       IPopupChooserBuilder<RunnerAndConfigurationSettings> builder = JBPopupFactory.getInstance()
549         .createPopupChooserBuilder(runConfigs)
550         .setRenderer(new DefaultListCellRenderer() {
551           @Override
552           public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
553             RunnerAndConfigurationSettings runConfig = (RunnerAndConfigurationSettings)value;
554             JLabel result = (JLabel)super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
555             result.setIcon(runConfig.getConfiguration().getIcon());
556             result.setText(runConfig.getName());
557             return result;
558           }
559         })
560         .setItemChosenCallback(runConfig -> ExecutionUtil.doRunConfiguration(runConfig, myExecutor, null, null, e.getDataContext()));
561
562       InputEvent inputEvent = e.getInputEvent();
563       if (inputEvent instanceof MouseEvent) {
564         builder.createPopup().showUnderneathOf(inputEvent.getComponent());
565       }
566       else {
567         Editor editor = FileEditorManager.getInstance(Objects.requireNonNull(e.getProject())).getSelectedTextEditor();
568         if (editor == null) {
569           // Not expected to happen because we are running a file from the current editor.
570           LOG.warn("Run Current File (" + runConfigs + "): getSelectedTextEditor() == null");
571           return;
572         }
573
574         builder
575           .setTitle(myExecutor.getActionName())
576           .createPopup()
577           .showInBestPositionFor(editor);
578       }
579     }
580   }
581
582   private static class RunCurrentFileInfo {
583     private final long myPsiModCount;
584     private final @NotNull List<RunnerAndConfigurationSettings> myRunConfigs;
585
586     private RunCurrentFileInfo(long psiModCount, @NotNull List<RunnerAndConfigurationSettings> runConfigs) {
587       myPsiModCount = psiModCount;
588       myRunConfigs = runConfigs;
589     }
590   }
591
592   @ApiStatus.Internal
593   public static class ExecutorGroupActionGroup extends ActionGroup implements DumbAware, UpdateInBackground {
594     protected final ExecutorGroup<?> myExecutorGroup;
595     private final Function<? super Executor, ? extends AnAction> myChildConverter;
596
597     protected ExecutorGroupActionGroup(@NotNull ExecutorGroup<?> executorGroup,
598                                        @NotNull Function<? super Executor, ? extends AnAction> childConverter) {
599       myExecutorGroup = executorGroup;
600       myChildConverter = childConverter;
601     }
602
603     @Override
604     public AnAction @NotNull [] getChildren(@Nullable AnActionEvent e) {
605       // RunExecutorSettings configurations can be modified, so we request current childExecutors on each call
606       List<Executor> childExecutors = myExecutorGroup.childExecutors();
607       AnAction[] result = new AnAction[childExecutors.size()];
608       for (int i = 0; i < childExecutors.size(); i++) {
609         result[i] = myChildConverter.apply(childExecutors.get(i));
610       }
611       return result;
612     }
613
614     @Override
615     public void update(@NotNull AnActionEvent e) {
616       final Project project = e.getProject();
617       if (project == null || !project.isInitialized() || project.isDisposed()) {
618         e.getPresentation().setEnabled(false);
619         return;
620       }
621       e.getPresentation().setEnabledAndVisible(myExecutorGroup.isApplicable(project));
622     }
623   }
624
625   public static final class RunnerHelper {
626     public static void run(@NotNull Project project,
627                            @Nullable RunConfiguration configuration,
628                            @Nullable RunnerAndConfigurationSettings settings,
629                            @NotNull DataContext dataContext,
630                            @NotNull Executor executor) {
631
632       runSubProcess(project, configuration, settings, dataContext, executor, RunToolbarProcessData.prepareBaseSettingCustomization(settings, null));
633     }
634
635     public static void runSubProcess(@NotNull Project project,
636                                      @Nullable RunConfiguration configuration,
637                                      @Nullable RunnerAndConfigurationSettings settings,
638                                      @NotNull DataContext dataContext,
639                                      @NotNull Executor executor,
640                                      @Nullable Consumer<ExecutionEnvironment> environmentCustomization) {
641
642       if (configuration instanceof CompoundRunConfiguration) {
643         RunManager runManager = RunManager.getInstance(project);
644         for (SettingsAndEffectiveTarget settingsAndEffectiveTarget : ((CompoundRunConfiguration)configuration)
645           .getConfigurationsWithEffectiveRunTargets()) {
646           RunConfiguration subConfiguration = settingsAndEffectiveTarget.getConfiguration();
647           runSubProcess(project, subConfiguration, runManager.findSettings(subConfiguration), dataContext, executor, environmentCustomization);
648         }
649       }
650       else {
651         ExecutionEnvironmentBuilder builder = settings == null ? null : ExecutionEnvironmentBuilder.createOrNull(executor, settings);
652         if (builder == null) {
653           return;
654         }
655         ExecutionEnvironment environment = builder.activeTarget().dataContext(dataContext).build();
656         if(environmentCustomization != null) environmentCustomization.accept(environment);
657         ExecutionManager.getInstance(project).restartRunProfile(environment);
658       }
659     }
660
661     public static boolean canRun(@NotNull Project project, @NotNull List<SettingsAndEffectiveTarget> pairs, @NotNull Executor executor) {
662       if (pairs.isEmpty()) {
663         return false;
664       }
665
666       for (SettingsAndEffectiveTarget pair : pairs) {
667         RunConfiguration configuration = pair.getConfiguration();
668         if (configuration instanceof CompoundRunConfiguration) {
669           if (!canRun(project, ((CompoundRunConfiguration)configuration).getConfigurationsWithEffectiveRunTargets(), executor)) {
670             return false;
671           }
672           continue;
673         }
674
675         ProgramRunner<?> runner = ProgramRunner.getRunner(executor.getId(), configuration);
676         if (runner == null
677             || !ExecutionTargetManager.canRun(configuration, pair.getTarget())
678             || ExecutionManager.getInstance(project).isStarting(executor.getId(), runner.getRunnerId())) {
679           return false;
680         }
681       }
682       return true;
683     }
684   }
685
686   private static class RunCurrentFileActionStatus {
687     private final boolean myEnabled;
688     private final @Nls @NotNull String myTooltip;
689     private final @NotNull Icon myIcon;
690
691     private final @NotNull List<RunnerAndConfigurationSettings> myRunConfigs;
692
693     private static RunCurrentFileActionStatus createDisabled(@Nls @NotNull String tooltip, @NotNull Icon icon) {
694       return new RunCurrentFileActionStatus(false, tooltip, icon, Collections.emptyList());
695     }
696
697     private static RunCurrentFileActionStatus createEnabled(@Nls @NotNull String tooltip,
698                                                             @NotNull Icon icon,
699                                                             @NotNull List<RunnerAndConfigurationSettings> runConfigs) {
700       return new RunCurrentFileActionStatus(true, tooltip, icon, runConfigs);
701     }
702
703     private RunCurrentFileActionStatus(boolean enabled,
704                                        @Nls @NotNull String tooltip,
705                                        @NotNull Icon icon,
706                                        @NotNull List<RunnerAndConfigurationSettings> runConfigs) {
707       myEnabled = enabled;
708       myTooltip = tooltip;
709       myIcon = icon;
710       myRunConfigs = runConfigs;
711     }
712   }
713 }