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;
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.*;
49 import java.awt.event.InputEvent;
50 import java.awt.event.MouseEvent;
51 import java.util.List;
53 import java.util.function.Consumer;
54 import java.util.function.Function;
56 public final class ExecutorRegistryImpl extends ExecutorRegistry {
57 private static final Logger LOG = Logger.getInstance(ExecutorRegistryImpl.class);
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";
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<>();
67 private final Map<String, AnAction> myRunWidgetIdToAction = new HashMap<>();
69 public ExecutorRegistryImpl() {
70 Executor.EXECUTOR_EXTENSION_NAME.addExtensionPointListener(new ExtensionPointListener<>() {
72 public void extensionAdded(@NotNull Executor extension, @NotNull PluginDescriptor pluginDescriptor) {
73 //noinspection TestOnlyProblems
74 initExecutorActions(extension, ActionManager.getInstance());
78 public void extensionRemoved(@NotNull Executor extension, @NotNull PluginDescriptor pluginDescriptor) {
79 deinitExecutor(extension);
84 final static class ExecutorRegistryActionConfigurationTuner implements ActionConfigurationCustomizer {
86 public void customize(@NotNull ActionManager manager) {
87 if (Executor.EXECUTOR_EXTENSION_NAME.hasAnyExtensions()) {
88 ((ExecutorRegistryImpl)getInstance()).init(manager);
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!");
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);
114 toolbarAction = new ExecutorAction(executor);
115 runContextAction = new RunContextAction(executor);
116 runNonExistingContextAction = new RunNewConfigurationContextAction(executor);
119 Executor.ActionWrapper customizer = executor.runnerActionsGroupExecutorActionCustomizer();
120 registerActionInGroup(actionManager, executor.getId(), customizer == null ? toolbarAction : customizer.wrap(toolbarAction), RUNNERS_GROUP, myIdToAction);
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);
128 ((DefaultActionGroup)actionManager.getAction(RUN_CONTEXT_GROUP_MORE))
129 .add(action, new Constraints(Anchor.BEFORE, "CreateRunConfiguration"), actionManager);
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);
136 initRunToolbarExecutorActions(executor, actionManager);
138 myContextActionIdSet.add(executor.getContextActionId());
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) {
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());
155 registerActionInGroup(actionManager, process.getActionId(), wrappedAction, RunToolbarProcess.RUN_WIDGET_GROUP,
156 myRunWidgetIdToAction);
159 RunToolbarAdditionActionsHolder holder = new RunToolbarAdditionActionsHolder(executorGroup, process);
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);
170 if (!process.isTemporaryProcess() && process.getShowInBar()) {
171 ExecutorAction wrappedAction = new RunToolbarProcessAction(process, executor);
172 ExecutorAction wrappedMainAction = new RunToolbarProcessMainAction(process, executor);
174 registerActionInGroup(actionManager, process.getActionId(), wrappedAction, RunToolbarProcess.RUN_WIDGET_GROUP,
175 myRunWidgetIdToAction);
177 registerActionInGroup(actionManager, process.getMainActionId(), wrappedMainAction, RunToolbarProcess.RUN_WIDGET_MAIN_GROUP,
178 myRunWidgetIdToAction);
186 private static String newConfigurationContextActionId(@NotNull Executor executor) {
187 return "newConfiguration" + executor.getContextActionId();
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);
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);
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);
213 synchronized void deinitExecutor(@NotNull Executor executor) {
214 myContextActionIdSet.remove(executor.getContextActionId());
216 unregisterAction(executor.getId(), RUNNERS_GROUP, myIdToAction);
217 if (isExecutorInMainGroup(executor)) {
218 unregisterAction(executor.getContextActionId(), RUN_CONTEXT_GROUP, myContextActionIdToAction);
221 unregisterAction(executor.getContextActionId(), RUN_CONTEXT_GROUP_MORE, myContextActionIdToAction);
223 unregisterAction(newConfigurationContextActionId(executor), RUN_CONTEXT_GROUP_MORE, myContextActionIdToAction);
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);
229 if (executor instanceof ExecutorGroup) {
230 unregisterAction(RunToolbarAdditionActionsHolder.getAdditionActionId(process), process.getMoreActionSubGroupName(),
231 myRunWidgetIdToAction);
232 unregisterAction(RunToolbarAdditionActionsHolder.getAdditionActionChooserGroupId(process), process.getMoreActionSubGroupName(),
233 myRunWidgetIdToAction);
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);
245 AnAction action = map.get(actionId);
246 if (action != null) {
247 group.remove(action, actionManager);
248 actionManager.unregisterAction(actionId);
249 map.remove(actionId);
252 action = ActionManager.getInstance().getAction(actionId);
253 if (action != null) {
254 group.remove(action, actionManager);
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())) {
270 private void init(@NotNull ActionManager actionManager) {
271 for (Executor executor : Executor.EXECUTOR_EXTENSION_NAME.getExtensionList()) {
273 //noinspection TestOnlyProblems
274 initExecutorActions(executor, actionManager);
276 catch (Throwable t) {
277 LOG.error("executor initialization failed: " + executor.getClass().getName(), t);
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");
285 protected final Executor myExecutor;
287 protected ExecutorAction(@NotNull Executor executor) {
288 super(executor.getStartActionText(), executor.getDescription(), IconLoader.createLazy(() -> executor.getIcon()));
289 myExecutor = executor;
292 private boolean canRun(@NotNull Project project, @NotNull List<SettingsAndEffectiveTarget> pairs) {
293 return RunnerHelper.canRun(project, pairs, myExecutor);
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);
305 RunnerAndConfigurationSettings selectedSettings = getSelectedConfiguration(e);
306 boolean enabled = false;
307 boolean hideDisabledExecutorButtons = false;
309 if (selectedSettings != null) {
310 if (DumbService.isDumb(project) && !selectedSettings.getType().isDumbAware()) {
311 presentation.setEnabled(false);
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());
322 ExecutionTarget target = ExecutionTargetManager.getActiveTarget(project);
323 enabled = canRun(project, Collections.singletonList(new SettingsAndEffectiveTarget(configuration, target)));
326 if (!(configuration instanceof CompoundRunConfiguration)) {
327 hideDisabledExecutorButtons = configuration.hideDisabledExecutorButtons();
330 presentation.setDescription(myExecutor.getDescription());
332 text = myExecutor.getStartActionText(configuration.getName());
335 if (RunConfigurationsComboBoxAction.hasRunCurrentFileItem(project)) {
336 RunCurrentFileActionStatus status = getRunCurrentFileActionStatus(e, false);
337 enabled = status.myEnabled;
338 text = status.myTooltip;
339 presentation.setIcon(status.myIcon);
342 text = getTemplatePresentation().getTextWithMnemonic();
346 if (hideDisabledExecutorButtons) {
347 presentation.setEnabledAndVisible(enabled);
350 presentation.setEnabled(enabled);
353 if (presentation.isVisible()) {
354 presentation.setVisible(myExecutor.isApplicable(project));
356 presentation.setText(text);
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());
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());
374 return getRunCurrentFileActionStatus(psiFile, resetCache);
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());
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());
391 return getRunCurrentFileActionStatus(psiFile, resetCache);
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());
401 List<RunnerAndConfigurationSettings> runnableConfigs = filterConfigsThatHaveRunner(runConfigs);
402 if (runnableConfigs.isEmpty()) {
403 return RunCurrentFileActionStatus.createDisabled(myExecutor.getStartActionText(psiFile.getName()), myExecutor.getIcon());
406 Icon icon = myExecutor.getIcon();
407 if (runnableConfigs.size() == 1) {
408 icon = getInformativeIcon(psiFile.getProject(), runnableConfigs.get(0));
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)) {
422 return RunCurrentFileActionStatus.createEnabled(myExecutor.getStartActionText(psiFile.getName()), icon, runnableConfigs);
425 private static List<RunnerAndConfigurationSettings> getRunConfigsForCurrentFile(@NotNull PsiFile psiFile, boolean resetCache) {
427 psiFile.putUserData(CURRENT_FILE_RUN_CONFIGS_KEY, null);
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
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.
441 long psiModCount = PsiModificationTracker.getInstance(psiFile.getProject()).getModificationCount();
442 RunCurrentFileInfo cache = psiFile.getUserData(CURRENT_FILE_RUN_CONFIGS_KEY);
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);
448 // The 'Run current file' feature doesn't reuse existing run configurations (by design).
449 List<ConfigurationFromContext> configurationsFromContext = configurationContext.createConfigurationsFromContext();
451 List<RunnerAndConfigurationSettings> runConfigs =
452 configurationsFromContext != null
453 ? ContainerUtil.map(configurationsFromContext, ConfigurationFromContext::getConfigurationSettings)
454 : Collections.emptyList();
456 VirtualFile vFile = psiFile.getVirtualFile();
457 String filePath = vFile != null ? vFile.getPath() : null;
458 for (RunnerAndConfigurationSettings config : runConfigs) {
459 ((RunnerAndConfigurationSettingsImpl)config).setFilePathIfRunningCurrentFile(filePath);
462 cache = new RunCurrentFileInfo(psiModCount, runConfigs);
463 psiFile.putUserData(CURRENT_FILE_RUN_CONFIGS_KEY, cache);
466 return cache.myRunConfigs;
469 private @NotNull List<RunnerAndConfigurationSettings> filterConfigsThatHaveRunner(@NotNull List<RunnerAndConfigurationSettings> runConfigs) {
470 return ContainerUtil.filter(runConfigs, config -> ProgramRunner.getRunner(myExecutor.getId(), config.getConfiguration()) != null);
473 private static boolean isSuppressed(Project project) {
474 for (ExecutionActionSuppressor suppressor : ExecutionActionSuppressor.EP_NAME.getExtensionList()) {
475 if (suppressor.isSuppressed(project)) return true;
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);
491 List<RunContentDescriptor> runningDescriptors =
492 executionManager.getRunningDescriptors(s -> ExecutionManagerImplKt.isOfSameType(s, selectedConfiguration));
493 runningDescriptors = ContainerUtil.filter(runningDescriptors, descriptor -> executionManager.getExecutors(descriptor).contains(myExecutor));
495 if (!configuration.isAllowRunningInParallel() && !runningDescriptors.isEmpty() && DefaultRunExecutor.EXECUTOR_ID.equals(myExecutor.getId())) {
496 return AllIcons.Actions.Restart;
498 if (runningDescriptors.isEmpty()) {
499 return myExecutor.getIcon();
502 if (runningDescriptors.size() == 1) {
503 return ExecutionUtil.getLiveIndicator(myExecutor.getIcon());
506 return IconUtil.addText(myExecutor.getIcon(), Integer.toString(runningDescriptors.size()));
511 protected RunnerAndConfigurationSettings getSelectedConfiguration(@NotNull AnActionEvent e) {
512 if(e.getProject() == null ) return null;
513 return RunManager.getInstance(e.getProject()).getSelectedConfiguration();
516 private void run(@NotNull Project project, @Nullable RunConfiguration configuration, @Nullable RunnerAndConfigurationSettings settings, @NotNull DataContext dataContext) {
517 RunnerHelper.run(project, configuration, settings, dataContext, myExecutor);
521 public void actionPerformed(@NotNull AnActionEvent e) {
522 final Project project = e.getProject();
523 if (project == null || project.isDisposed()) {
527 MacroManager.getInstance().cacheMacrosPreview(e.getDataContext());
528 RunnerAndConfigurationSettings selectedConfiguration = getSelectedConfiguration(e);
529 if (selectedConfiguration != null) {
530 run(project, selectedConfiguration.getConfiguration(), selectedConfiguration, e.getDataContext());
532 else if (RunConfigurationsComboBoxAction.hasRunCurrentFileItem(project)) {
537 private void runCurrentFile(@NotNull AnActionEvent e) {
538 List<RunnerAndConfigurationSettings> runConfigs = getRunCurrentFileActionStatus(e, true).myRunConfigs;
539 if (runConfigs.isEmpty()) {
543 if (runConfigs.size() == 1) {
544 ExecutionUtil.doRunConfiguration(runConfigs.get(0), myExecutor, null, null, e.getDataContext());
548 IPopupChooserBuilder<RunnerAndConfigurationSettings> builder = JBPopupFactory.getInstance()
549 .createPopupChooserBuilder(runConfigs)
550 .setRenderer(new DefaultListCellRenderer() {
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());
560 .setItemChosenCallback(runConfig -> ExecutionUtil.doRunConfiguration(runConfig, myExecutor, null, null, e.getDataContext()));
562 InputEvent inputEvent = e.getInputEvent();
563 if (inputEvent instanceof MouseEvent) {
564 builder.createPopup().showUnderneathOf(inputEvent.getComponent());
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");
575 .setTitle(myExecutor.getActionName())
577 .showInBestPositionFor(editor);
582 private static class RunCurrentFileInfo {
583 private final long myPsiModCount;
584 private final @NotNull List<RunnerAndConfigurationSettings> myRunConfigs;
586 private RunCurrentFileInfo(long psiModCount, @NotNull List<RunnerAndConfigurationSettings> runConfigs) {
587 myPsiModCount = psiModCount;
588 myRunConfigs = runConfigs;
593 public static class ExecutorGroupActionGroup extends ActionGroup implements DumbAware, UpdateInBackground {
594 protected final ExecutorGroup<?> myExecutorGroup;
595 private final Function<? super Executor, ? extends AnAction> myChildConverter;
597 protected ExecutorGroupActionGroup(@NotNull ExecutorGroup<?> executorGroup,
598 @NotNull Function<? super Executor, ? extends AnAction> childConverter) {
599 myExecutorGroup = executorGroup;
600 myChildConverter = childConverter;
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));
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);
621 e.getPresentation().setEnabledAndVisible(myExecutorGroup.isApplicable(project));
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) {
632 runSubProcess(project, configuration, settings, dataContext, executor, RunToolbarProcessData.prepareBaseSettingCustomization(settings, null));
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) {
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);
651 ExecutionEnvironmentBuilder builder = settings == null ? null : ExecutionEnvironmentBuilder.createOrNull(executor, settings);
652 if (builder == null) {
655 ExecutionEnvironment environment = builder.activeTarget().dataContext(dataContext).build();
656 if(environmentCustomization != null) environmentCustomization.accept(environment);
657 ExecutionManager.getInstance(project).restartRunProfile(environment);
661 public static boolean canRun(@NotNull Project project, @NotNull List<SettingsAndEffectiveTarget> pairs, @NotNull Executor executor) {
662 if (pairs.isEmpty()) {
666 for (SettingsAndEffectiveTarget pair : pairs) {
667 RunConfiguration configuration = pair.getConfiguration();
668 if (configuration instanceof CompoundRunConfiguration) {
669 if (!canRun(project, ((CompoundRunConfiguration)configuration).getConfigurationsWithEffectiveRunTargets(), executor)) {
675 ProgramRunner<?> runner = ProgramRunner.getRunner(executor.getId(), configuration);
677 || !ExecutionTargetManager.canRun(configuration, pair.getTarget())
678 || ExecutionManager.getInstance(project).isStarting(executor.getId(), runner.getRunnerId())) {
686 private static class RunCurrentFileActionStatus {
687 private final boolean myEnabled;
688 private final @Nls @NotNull String myTooltip;
689 private final @NotNull Icon myIcon;
691 private final @NotNull List<RunnerAndConfigurationSettings> myRunConfigs;
693 private static RunCurrentFileActionStatus createDisabled(@Nls @NotNull String tooltip, @NotNull Icon icon) {
694 return new RunCurrentFileActionStatus(false, tooltip, icon, Collections.emptyList());
697 private static RunCurrentFileActionStatus createEnabled(@Nls @NotNull String tooltip,
699 @NotNull List<RunnerAndConfigurationSettings> runConfigs) {
700 return new RunCurrentFileActionStatus(true, tooltip, icon, runConfigs);
703 private RunCurrentFileActionStatus(boolean enabled,
704 @Nls @NotNull String tooltip,
706 @NotNull List<RunnerAndConfigurationSettings> runConfigs) {
710 myRunConfigs = runConfigs;