diff --git a/plugins/gradle/gradle.iml b/plugins/gradle/gradle.iml index 925c1ab193ac..4dbad25039f1 100644 --- a/plugins/gradle/gradle.iml +++ b/plugins/gradle/gradle.iml @@ -6,6 +6,7 @@ + @@ -176,6 +177,7 @@ + diff --git a/plugins/gradle/resources/i18n/GradleBundle.properties b/plugins/gradle/resources/i18n/GradleBundle.properties index 67e121dfebea..26ed2bc1f194 100644 --- a/plugins/gradle/resources/i18n/GradleBundle.properties +++ b/plugins/gradle/resources/i18n/GradleBundle.properties @@ -22,4 +22,8 @@ gradle.action.open.script.description=Allows to open project file of the linked gradle.notifications.use.distribution.with.sources=You can configure Gradle wrapper to use distribution with sources. \ It will provide IDE with Gradle API/DSL documentation. gradle.notifications.hide.tip=Hide the tip -gradle.notifications.apply.suggestion=Ok, apply suggestion! \ No newline at end of file +gradle.notifications.apply.suggestion=Ok, apply suggestion! + +gradle.codeInsight.action.apply_plugin.text=Add Gradle plugin +gradle.codeInsight.action.apply_plugin.description=Add selected gradle plugin to the project +gradle.codeInsight.action.apply_plugin.popup.title=Choose plugin \ No newline at end of file diff --git a/plugins/gradle/resources/i18n/GradleDocumentationBundle.properties b/plugins/gradle/resources/i18n/GradleDocumentationBundle.properties index 857b741c2b60..1c185408c4dc 100644 --- a/plugins/gradle/resources/i18n/GradleDocumentationBundle.properties +++ b/plugins/gradle/resources/i18n/GradleDocumentationBundle.properties @@ -42,13 +42,13 @@ gradle.documentation.org.gradle.api.Project.apply.plugin.announce=\ gradle.documentation.org.gradle.api.Project.apply.plugin.build-announcements=\

Sends local announcements to your desktop about interesting events in the build lifecycle.

gradle.documentation.org.gradle.api.Project.apply.plugin.checkstyle=\ -

Performs quality checks on your project's Java source files using\ +

Performs quality checks on your project's Java source files using \ Checkstyle \ and generates reports from these checks.\

gradle.documentation.org.gradle.api.Project.apply.plugin.codenarc=\

Performs quality checks on your project's Groovy source files using \ - CodeNarc\ + CodeNarc \ and generates reports from these checks.\

gradle.documentation.org.gradle.api.Project.apply.plugin.eclipse=\ @@ -62,7 +62,7 @@ gradle.documentation.org.gradle.api.Project.apply.plugin.eclipse-wtp=\

gradle.documentation.org.gradle.api.Project.apply.plugin.findbugs=\

Performs quality checks on your project's Java source files using \ - FindBugs\ + FindBugs \ and generates reports from these checks.\

gradle.documentation.org.gradle.api.Project.apply.plugin.idea=\ @@ -76,7 +76,7 @@ gradle.documentation.org.gradle.api.Project.apply.plugin.jdepend=\

gradle.documentation.org.gradle.api.Project.apply.plugin.pmd=\

Performs quality checks on your project's Java source files using \ - PMD\ + PMD \ and generates reports from these checks.\

gradle.documentation.org.gradle.api.Project.apply.plugin.project-report=\ diff --git a/plugins/gradle/resources/icons/gradlePlugin.png b/plugins/gradle/resources/icons/gradlePlugin.png new file mode 100644 index 000000000000..a342f298cc99 Binary files /dev/null and b/plugins/gradle/resources/icons/gradlePlugin.png differ diff --git a/plugins/gradle/src/META-INF/plugin.xml b/plugins/gradle/src/META-INF/plugin.xml index fd2e97e78fa4..9cb03439b612 100644 --- a/plugins/gradle/src/META-INF/plugin.xml +++ b/plugins/gradle/src/META-INF/plugin.xml @@ -93,5 +93,10 @@ - + + + + + + diff --git a/plugins/gradle/src/icons/GradleIcons.java b/plugins/gradle/src/icons/GradleIcons.java index e12bafb0d0c2..4283f7207364 100644 --- a/plugins/gradle/src/icons/GradleIcons.java +++ b/plugins/gradle/src/icons/GradleIcons.java @@ -31,6 +31,7 @@ public class GradleIcons { public static final Icon Gradle = load("/icons/gradle.png"); // 16x16 public static final Icon GradleImport = load("/icons/gradleImport.png"); // 16x16 public static final Icon GradleNavigate = load("/icons/gradleNavigate.png"); // 16x16 + public static final Icon GradlePlugin = load("/icons/gradlePlugin.png"); // 16x16 public static final Icon GradleSync = load("/icons/gradleSync.png"); // 16x16 public static final Icon ToolWindowGradle = load("/icons/toolWindowGradle.png"); // 13x13 } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/codeInsight/actions/AddGradleDslPluginAction.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/codeInsight/actions/AddGradleDslPluginAction.java new file mode 100644 index 000000000000..fa9bcf7a9064 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/codeInsight/actions/AddGradleDslPluginAction.java @@ -0,0 +1,90 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.plugins.gradle.codeInsight.actions; + +import com.intellij.codeInsight.CodeInsightActionHandler; +import com.intellij.codeInsight.actions.CodeInsightAction; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.KeyValue; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiCompiledElement; +import com.intellij.psi.PsiFile; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; +import icons.GradleIcons; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.gradle.util.GradleBundle; +import org.jetbrains.plugins.gradle.util.GradleConstants; +import org.jetbrains.plugins.gradle.util.GradleDocumentationBundle; +import org.jetbrains.plugins.groovy.GroovyFileType; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +/** + * @author Vladislav.Soroka + * @since 10/22/13 + */ +public class AddGradleDslPluginAction extends CodeInsightAction { + public static final ThreadLocal TEST_THREAD_LOCAL = new ThreadLocal(); + private final KeyValue[] myPlugins; + + public AddGradleDslPluginAction() { + getTemplatePresentation().setDescription(GradleBundle.message("gradle.codeInsight.action.apply_plugin.description")); + getTemplatePresentation().setText(GradleBundle.message("gradle.codeInsight.action.apply_plugin.text")); + getTemplatePresentation().setIcon(GradleIcons.GradlePlugin); + + final List plugins = StringUtil.split( + "java,groovy,idea,eclipse,scala,antlr,application,ear,jetty,maven,osgi,war,announce," + + "build-announcements,checkstyle,codenarc,eclipse-wtp,findbugs,jdepend,pmd,project-report,signing,sonar", ","); + + myPlugins = new KeyValue[plugins.size()]; + ContainerUtil.map2Array(plugins, myPlugins, new Function() { + @Override + public KeyValue fun(String o) { + return createPluginKey(o); + } + }); + Arrays.sort(myPlugins, new Comparator() { + @Override + public int compare(KeyValue o1, KeyValue o2) { + return String.valueOf(o1.getKey()).compareTo(String.valueOf(o2.getKey())); + } + }); + } + + @NotNull + @Override + protected CodeInsightActionHandler getHandler() { + return new AddGradleDslPluginActionHandler(myPlugins); + } + + @Override + protected boolean isValidForFile(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) { + if (file instanceof PsiCompiledElement) return false; + if (!GroovyFileType.GROOVY_FILE_TYPE.equals(file.getFileType())) return false; + return !GradleConstants.SETTINGS_FILE_NAME.equals(file.getName()) && file.getName().endsWith(GradleConstants.EXTENSION); + } + + @NotNull + private static KeyValue createPluginKey(@NotNull String pluginName) { + String description = + GradleDocumentationBundle.messageOrDefault("gradle.documentation.org.gradle.api.Project.apply.plugin." + pluginName, ""); + return KeyValue.create(pluginName, description); + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/codeInsight/actions/AddGradleDslPluginActionHandler.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/codeInsight/actions/AddGradleDslPluginActionHandler.java new file mode 100644 index 000000000000..540fdc9a878e --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/codeInsight/actions/AddGradleDslPluginActionHandler.java @@ -0,0 +1,165 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.plugins.gradle.codeInsight.actions; + +import com.intellij.codeInsight.CodeInsightActionHandler; +import com.intellij.codeInsight.CodeInsightUtilBase; +import com.intellij.codeInsight.lookup.impl.LookupCellRenderer; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.colors.EditorColorsManager; +import com.intellij.openapi.editor.colors.EditorColorsScheme; +import com.intellij.openapi.editor.colors.EditorFontType; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.KeyValue; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.ui.components.JBList; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.gradle.util.GradleBundle; +import org.jetbrains.plugins.groovy.lang.psi.GroovyFile; +import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrStatement; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock; + +import javax.swing.*; +import java.awt.*; + +/** +* @author Vladislav.Soroka +* @since 10/24/13 +*/ +class AddGradleDslPluginActionHandler implements CodeInsightActionHandler { + private final KeyValue[] myPlugins; + public AddGradleDslPluginActionHandler(KeyValue[] plugins) { + myPlugins = plugins; + } + + @Override + public void invoke(@NotNull final Project project, @NotNull final Editor editor, @NotNull final PsiFile file) { + if (!CodeInsightUtilBase.prepareEditorForWrite(editor)) return; + + final JBList list = new JBList(myPlugins); + list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + list.setCellRenderer(new MyListCellRenderer()); + Runnable runnable = new Runnable() { + @Override + public void run() { + final KeyValue selected = (KeyValue)list.getSelectedValue(); + new WriteCommandAction.Simple(project, GradleBundle.message("gradle.codeInsight.action.apply_plugin.text"), file) { + @Override + protected void run() { + if (selected == null) return; + GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(project); + GrStatement grStatement = factory.createStatementFromText( + String.format("apply plugin: '%s'", selected.getKey()), null); + + PsiElement anchor = file.findElementAt(editor.getCaretModel().getOffset()); + PsiElement currentElement = PsiTreeUtil.getParentOfType(anchor, GrClosableBlock.class, GroovyFile.class); + if (currentElement != null) { + currentElement.addAfter(grStatement, anchor); + } + else { + file.addAfter(grStatement, file.findElementAt(editor.getCaretModel().getOffset() - 1)); + } + PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project); + Document document = documentManager.getDocument(file); + if (document != null) { + documentManager.commitDocument(document); + } + } + }.execute(); + } + }; + + if (ApplicationManager.getApplication().isUnitTestMode()) { + KeyValue descriptor = ContainerUtil.find(myPlugins, new Condition() { + @Override + public boolean value(KeyValue value) { + return value.getKey().equals(AddGradleDslPluginAction.TEST_THREAD_LOCAL.get()); + } + }); + list.setSelectedValue(descriptor, false); + runnable.run(); + } + else { + JBPopupFactory.getInstance().createListPopupBuilder(list) + .setTitle(GradleBundle.message("gradle.codeInsight.action.apply_plugin.popup.title")) + .setItemChoosenCallback(runnable) + .setFilteringEnabled(new Function() { + @Override + public String fun(Object o) { + return String.valueOf(((KeyValue)o).getKey()); + } + }) + .createPopup() + .showInBestPositionFor(editor); + } + } + + @Override + public boolean startInWriteAction() { + return false; + } + + private static class MyListCellRenderer implements ListCellRenderer { + private final JPanel myPanel; + private final JLabel myNameLabel; + private final JLabel myDescLabel; + + public MyListCellRenderer() { + myPanel = new JPanel(new BorderLayout()); + myPanel.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 0)); + myNameLabel = new JLabel(); + + myPanel.add(myNameLabel, BorderLayout.WEST); + myPanel.add(new JLabel(" ")); + myDescLabel = new JLabel(); + myPanel.add(myDescLabel, BorderLayout.EAST); + + EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme(); + Font font = scheme.getFont(EditorFontType.PLAIN); + myNameLabel.setFont(font); + myDescLabel.setFont(font); + } + + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + + KeyValue descriptor = (KeyValue)value; + Color backgroundColor = isSelected ? list.getSelectionBackground() : list.getBackground(); + + myNameLabel.setText(String.valueOf(descriptor.getKey())); + myNameLabel.setForeground(isSelected ? list.getSelectionForeground() : list.getForeground()); + myPanel.setBackground(backgroundColor); + + String description = String.format("
%s
", 400, String.valueOf(descriptor.getValue())); + myDescLabel.setText(description); + myDescLabel.setForeground(LookupCellRenderer.getGrayedForeground(isSelected)); + myDescLabel.setBackground(backgroundColor); + + return myPanel; + } + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/codeInsight/actions/GradleGenerateActionGroup.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/codeInsight/actions/GradleGenerateActionGroup.java new file mode 100644 index 000000000000..18530edb7f99 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/codeInsight/actions/GradleGenerateActionGroup.java @@ -0,0 +1,28 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.plugins.gradle.codeInsight.actions; + +import com.intellij.openapi.actionSystem.DefaultActionGroup; + +/** + * @author Vladislav.Soroka + * @since 10/22/13 + */ +public class GradleGenerateActionGroup extends DefaultActionGroup { + public GradleGenerateActionGroup() { + add(new AddGradleDslPluginAction()); + } +} \ No newline at end of file diff --git a/plugins/gradle/testData/testApplyPlugin.gradle b/plugins/gradle/testData/testApplyPlugin.gradle new file mode 100644 index 000000000000..f90814d636ed --- /dev/null +++ b/plugins/gradle/testData/testApplyPlugin.gradle @@ -0,0 +1 @@ +//empty file \ No newline at end of file diff --git a/plugins/gradle/testData/testApplyPluginInsideClosure.gradle b/plugins/gradle/testData/testApplyPluginInsideClosure.gradle new file mode 100644 index 000000000000..6e96bac9c4a8 --- /dev/null +++ b/plugins/gradle/testData/testApplyPluginInsideClosure.gradle @@ -0,0 +1,6 @@ + +apply plugin: "groovy" + +allprojects { + // +} \ No newline at end of file diff --git a/plugins/gradle/testData/testApplyPluginInsideClosure_after.gradle b/plugins/gradle/testData/testApplyPluginInsideClosure_after.gradle new file mode 100644 index 000000000000..023847fe0b42 --- /dev/null +++ b/plugins/gradle/testData/testApplyPluginInsideClosure_after.gradle @@ -0,0 +1,6 @@ + +apply plugin: "groovy" + +allprojects { + // +apply plugin: 'java'} \ No newline at end of file diff --git a/plugins/gradle/testData/testApplyPlugin_after.gradle b/plugins/gradle/testData/testApplyPlugin_after.gradle new file mode 100644 index 000000000000..1dd49648e03c --- /dev/null +++ b/plugins/gradle/testData/testApplyPlugin_after.gradle @@ -0,0 +1,2 @@ +//empty file +apply plugin: 'java' \ No newline at end of file diff --git a/plugins/gradle/testSources/org/jetbrains/plugins/gradle/codeInsight/actions/AddGradleDslPluginActionTest.java b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/codeInsight/actions/AddGradleDslPluginActionTest.java new file mode 100644 index 000000000000..6e0e9edd07f2 --- /dev/null +++ b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/codeInsight/actions/AddGradleDslPluginActionTest.java @@ -0,0 +1,63 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.plugins.gradle.codeInsight.actions; + +import com.intellij.testFramework.IdeaTestCase; +import com.intellij.testFramework.PlatformTestUtil; +import com.intellij.testFramework.fixtures.CodeInsightTestUtil; +import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase; + +import java.io.File; + +/** + * @author Vladislav.Soroka + * @since 10/23/13 + */ +public class AddGradleDslPluginActionTest extends LightPlatformCodeInsightFixtureTestCase { + + public AddGradleDslPluginActionTest() { + IdeaTestCase.initPlatformPrefix(); + } + + public void testApplyPlugin() throws Exception { + doTest("testApplyPlugin.gradle", "java"); + } + + public void testApplyPluginInsideClosure() throws Exception { + myFixture.configureByFile("testApplyPluginInsideClosure.gradle"); + doTest("testApplyPluginInsideClosure.gradle", "java"); + } + + private void doTest(String file, String pluginName) { + AddGradleDslPluginAction.TEST_THREAD_LOCAL.set(pluginName); + CodeInsightTestUtil.doActionTest(new AddGradleDslPluginAction(), file, myFixture); + } + + @Override + public void setUp() throws Exception { + super.setUp(); + } + + @Override + protected String getBasePath() { + return "/plugins/gradle/testData"; + } + + @Override + protected String getTestDataPath() { + return PlatformTestUtil.getCommunityPath().replace(File.separatorChar, '/') + getBasePath(); + } +}