From a217e22325842352ba064af68df9039773efb06f Mon Sep 17 00:00:00 2001 From: "Vladislav.Soroka" Date: Thu, 9 Aug 2018 15:47:35 +0300 Subject: [PATCH] IDEA-196697 Support starting Gradle tasks via 'Run Anything' action --- .../plugin-resources/META-INF/plugin.xml | 2 + .../action/GradleExecuteTaskAction.java | 4 + .../execution/GradleRunAnythingHelpGroup.java | 26 +++ .../execution/GradleRunAnythingProvider.java | 179 ++++++++++++++++++ .../execution/RunAnythingGradleItem.java | 52 +++++ 5 files changed, 263 insertions(+) create mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/execution/GradleRunAnythingHelpGroup.java create mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/execution/GradleRunAnythingProvider.java create mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/execution/RunAnythingGradleItem.java diff --git a/plugins/gradle/plugin-resources/META-INF/plugin.xml b/plugins/gradle/plugin-resources/META-INF/plugin.xml index ed820de5b250..ca1bf825c26a 100644 --- a/plugins/gradle/plugin-resources/META-INF/plugin.xml +++ b/plugins/gradle/plugin-resources/META-INF/plugin.xml @@ -111,6 +111,8 @@ + + diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/action/GradleExecuteTaskAction.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/action/GradleExecuteTaskAction.java index 164e7fa3ffe3..35bf6cbc3660 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/action/GradleExecuteTaskAction.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/action/GradleExecuteTaskAction.java @@ -111,6 +111,10 @@ public class GradleExecuteTaskAction extends ExternalSystemAction { historyService.addCommand(fullCommandLine, workDirectory); + runGradle(project, workDirectory, fullCommandLine); + } + + public static void runGradle(@NotNull Project project, @NotNull String workDirectory, @NotNull String fullCommandLine) { final ExternalTaskExecutionInfo taskExecutionInfo; try { taskExecutionInfo = buildTaskInfo(workDirectory, fullCommandLine); diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/GradleRunAnythingHelpGroup.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/GradleRunAnythingHelpGroup.java new file mode 100644 index 000000000000..5bbc0aa50b5d --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/GradleRunAnythingHelpGroup.java @@ -0,0 +1,26 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package org.jetbrains.plugins.gradle.execution; + +import com.intellij.ide.actions.runAnything.activity.RunAnythingProvider; +import com.intellij.ide.actions.runAnything.groups.RunAnythingHelpGroup; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; + +/** + * @author Vladislav.Soroka + */ +public class GradleRunAnythingHelpGroup extends RunAnythingHelpGroup { + @NotNull + @Override + public String getTitle() { + return "Gradle"; + } + + @NotNull + @Override + public Collection getProviders() { + return ContainerUtil.immutableSingletonList(RunAnythingProvider.EP_NAME.findExtension(GradleRunAnythingProvider.class)); + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/GradleRunAnythingProvider.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/GradleRunAnythingProvider.java new file mode 100644 index 000000000000..85469575c876 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/GradleRunAnythingProvider.java @@ -0,0 +1,179 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package org.jetbrains.plugins.gradle.execution; + +import com.intellij.ide.actions.runAnything.activity.RunAnythingProviderBase; +import com.intellij.ide.actions.runAnything.items.RunAnythingItem; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.externalSystem.model.DataNode; +import com.intellij.openapi.externalSystem.model.ExternalProjectInfo; +import com.intellij.openapi.externalSystem.model.ProjectKeys; +import com.intellij.openapi.externalSystem.model.project.ModuleData; +import com.intellij.openapi.externalSystem.model.task.TaskData; +import com.intellij.openapi.externalSystem.service.project.ProjectDataManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.util.CachedValueProvider; +import com.intellij.psi.util.CachedValuesManager; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.MultiMap; +import groovyjarjarcommonscli.Option; +import icons.GradleIcons; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.gradle.action.GradleExecuteTaskAction; +import org.jetbrains.plugins.gradle.service.execution.cmd.GradleCommandLineOptionsProvider; +import org.jetbrains.plugins.gradle.settings.GradleProjectSettings; +import org.jetbrains.plugins.gradle.settings.GradleSettings; +import org.jetbrains.plugins.gradle.util.GradleConstants; + +import javax.swing.*; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static com.intellij.ide.actions.runAnything.RunAnythingUtil.fetchProject; +import static com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil.getChildren; + +/** + * @author Vladislav.Soroka + */ +public class GradleRunAnythingProvider extends RunAnythingProviderBase { + + @NotNull + @Override + public RunAnythingItem getMainListItem(@NotNull DataContext dataContext, @NotNull String value) { + return new RunAnythingGradleItem(getCommand(value), getIcon(value)); + } + + @Nullable + @Override + public String findMatchingValue(@NotNull DataContext dataContext, @NotNull String pattern) { + return pattern.startsWith(getHelpCommand()) ? getCommand(pattern) : null; + } + + @NotNull + @Override + public Collection getValues(@NotNull DataContext dataContext, @NotNull String pattern) { + if (!pattern.startsWith(getHelpCommand())) { + return Collections.emptyList(); + } + List result = ContainerUtil.newSmartList(); + + int spaceIndex = StringUtil.lastIndexOf(pattern, ' ', getHelpCommand().length(), pattern.length()); + String prefix = StringUtil.trim(spaceIndex < 0 ? getHelpCommand() : pattern.substring(0, spaceIndex + 1)) + ' '; + String toComplete = spaceIndex < 0 ? "" : pattern.substring(spaceIndex + 1); + + appendArgumentsVariants(result, prefix, toComplete); + if (!result.isEmpty()) return result; + + appendTasksVariants(result, prefix, dataContext); + return result; + } + + private static void appendTasksVariants(@NotNull List result, @NotNull String prefix, @NotNull DataContext dataContext) { + MultiMap tasks = fetchTasks(dataContext); + for (Map.Entry> entry : tasks.entrySet()) { + for (String taskName : entry.getValue()) { + String taskFqn = entry.getKey() + taskName; + result.add(prefix + taskFqn); + } + } + } + + private static void appendArgumentsVariants(@NotNull List result, @NotNull String prefix, @NotNull String toComplete) { + if (!toComplete.startsWith("-")) return; + + boolean isLongOpt = toComplete.startsWith("--"); + prefix += (isLongOpt ? "--" : "-"); + for (Object option : GradleCommandLineOptionsProvider.getSupportedOptions().getOptions()) { + if (option instanceof Option) { + String opt = isLongOpt ? ((Option)option).getLongOpt() : ((Option)option).getOpt(); + if (StringUtil.isNotEmpty(opt)) { + result.add(prefix + opt); + } + } + } + } + + @Override + public void execute(@NotNull DataContext dataContext, @NotNull String value) { + Project project = fetchProject(dataContext); + String commandLine = StringUtil.trimStart(value, getHelpCommand()); + for (GradleProjectSettings setting : GradleSettings.getInstance(project).getLinkedProjectsSettings()) { + GradleExecuteTaskAction.runGradle(project, setting.getExternalProjectPath(), commandLine); + } + } + + @NotNull + @Override + public String getCommand(@NotNull String value) { + return value; + } + + @Nullable + @Override + public Icon getIcon(@NotNull String value) { + return GradleIcons.Gradle; + } + + @NotNull + public String getCompletionGroupTitle() { + return "Gradle tasks"; + } + + @NotNull + @Override + public String getHelpCommandPlaceholder() { + return "gradle <--option-name...>"; + } + + @NotNull + @Override + public String getHelpCommand() { + return "gradle"; + } + + @Override + public Icon getHelpIcon() { + return GradleIcons.Gradle; + } + + private static MultiMap fetchTasks(@NotNull DataContext dataContext) { + Project project = fetchProject(dataContext); + return CachedValuesManager.getManager(project).getCachedValue( + project, () -> CachedValueProvider.Result.create(getTasksMap(project), ProjectRootManager.getInstance(project))); + } + + @NotNull + private static MultiMap getTasksMap(Project project) { + MultiMap tasks = MultiMap.createOrderedSet(); + for (GradleProjectSettings setting : GradleSettings.getInstance(project).getLinkedProjectsSettings()) { + final ExternalProjectInfo projectData = + ProjectDataManager.getInstance().getExternalProjectData(project, GradleConstants.SYSTEM_ID, setting.getExternalProjectPath()); + if (projectData == null || projectData.getExternalProjectStructure() == null) continue; + + for (DataNode moduleDataNode : getChildren(projectData.getExternalProjectStructure(), ProjectKeys.MODULE)) { + String gradlePath; + String moduleId = moduleDataNode.getData().getId(); + if (moduleId.charAt(0) != ':') { + int colonIndex = moduleId.indexOf(':'); + gradlePath = colonIndex > 0 ? moduleId.substring(colonIndex) : ":"; + } + else { + gradlePath = moduleId; + } + for (DataNode node : getChildren(moduleDataNode, ProjectKeys.TASK)) { + TaskData taskData = node.getData(); + String taskName = taskData.getName(); + if (StringUtil.isNotEmpty(taskName)) { + String taskPathPrefix = ":".equals(gradlePath) || taskName.startsWith(gradlePath) ? "" : (gradlePath + ':'); + tasks.putValue(taskPathPrefix, taskName); + } + } + } + } + return tasks; + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/RunAnythingGradleItem.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/RunAnythingGradleItem.java new file mode 100644 index 000000000000..39dcba327c3a --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/RunAnythingGradleItem.java @@ -0,0 +1,52 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package org.jetbrains.plugins.gradle.execution; + +import com.intellij.ide.actions.runAnything.items.RunAnythingItemBase; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.ui.SimpleColoredComponent; +import com.intellij.ui.SimpleTextAttributes; +import groovyjarjarcommonscli.Option; +import groovyjarjarcommonscli.Options; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.gradle.service.execution.cmd.GradleCommandLineOptionsProvider; + +import javax.swing.*; +import java.awt.*; + +/** + * @author Vladislav.Soroka + */ +public class RunAnythingGradleItem extends RunAnythingItemBase { + + public RunAnythingGradleItem(@NotNull String command, @Nullable Icon icon) { + super(command, icon); + } + + @NotNull + @Override + public Component createComponent(boolean isSelected) { + String command = getCommand(); + + int spaceIndex = StringUtil.lastIndexOf(command, ' ', 0, command.length()); + String toComplete = spaceIndex < 0 ? "" : command.substring(spaceIndex + 1); + String description = null; + if (toComplete.startsWith("-")) { + boolean isLongOpt = toComplete.startsWith("--"); + Options options = GradleCommandLineOptionsProvider.getSupportedOptions(); + Option option = options.getOption(toComplete.substring(isLongOpt ? 2 : 1)); + if (option != null) { + description = option.getDescription(); + } + } + SimpleColoredComponent component = new SimpleColoredComponent(); + component.append(command); + component.appendTextPadding(20); + setupIcon(component, myIcon); + + if (description != null) { + component.append(" " + StringUtil.shortenTextWithEllipsis(description, 200, 0), SimpleTextAttributes.GRAYED_ITALIC_ATTRIBUTES); + } + return component; + } +} \ No newline at end of file