From 6b3f5859139482df741d1bb0f31f2265c378e30b Mon Sep 17 00:00:00 2001 From: Dmitry Avdeev Date: Fri, 2 Mar 2012 13:32:55 +0400 Subject: [PATCH 01/13] cleanup --- .../impl/src/com/intellij/compiler/impl/CompileDriver.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java b/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java index 757d01cef826..7b22d47ba2e0 100644 --- a/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java +++ b/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java @@ -2065,10 +2065,6 @@ public class CompileDriver { try { for (FileProcessingCompiler.ProcessingItem item : items) { final VirtualFile file = item.getFile(); - if (file == null) { - LOG.error("FileProcessingCompiler.ProcessingItem.getFile() must not return null: compiler " + adapter.getCompiler().getDescription()); - continue; - } final String url = file.getUrl(); allUrls.add(url); if (!forceCompile && cache.getTimestamp(url) == file.getTimeStamp()) { From 4041105c1bd5c51b55a5c4294a8bbf6dcd9c59d0 Mon Sep 17 00:00:00 2001 From: Kirill Likhodedov Date: Thu, 1 Mar 2012 19:49:48 +0400 Subject: [PATCH 02/13] GitRootErrorsFinder + tests --- .../src/git4idea/roots/GitRootError.java | 66 +++++++++ .../git4idea/roots/GitRootErrorsFinder.java | 62 ++++++++ .../git4idea/roots/AbstractGitRootTest.groovy | 88 ++++++++++++ .../git4idea/roots/GitRootDetectorTest.groovy | 67 +-------- .../roots/GitRootErrorsFinderTest.groovy | 136 ++++++++++++++++++ .../git4idea/test/GitMockVcsManager.groovy | 4 + .../git4idea/test/GitMockVirtualFile.groovy | 11 ++ 7 files changed, 370 insertions(+), 64 deletions(-) create mode 100644 plugins/git4idea/src/git4idea/roots/GitRootError.java create mode 100644 plugins/git4idea/src/git4idea/roots/GitRootErrorsFinder.java create mode 100644 plugins/git4idea/tests/git4idea/roots/AbstractGitRootTest.groovy create mode 100644 plugins/git4idea/tests/git4idea/roots/GitRootErrorsFinderTest.groovy diff --git a/plugins/git4idea/src/git4idea/roots/GitRootError.java b/plugins/git4idea/src/git4idea/roots/GitRootError.java new file mode 100644 index 000000000000..b2b565484cd7 --- /dev/null +++ b/plugins/git4idea/src/git4idea/roots/GitRootError.java @@ -0,0 +1,66 @@ +/* + * Copyright 2000-2012 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 git4idea.roots; + +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; + +/** + * @author Kirill Likhodedov + */ +class GitRootError { + + private final Type myType; + @NotNull private final VirtualFile myRoot; + + enum Type { + EXTRA_ROOT, + UNREGISTERED_ROOT + } + + GitRootError(@NotNull Type type, @NotNull VirtualFile root) { + myType = type; + myRoot = root; + } + + @Override + public String toString() { + return "GitRootError{" + + "myType=" + myType + + ", myRoot=" + myRoot + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + GitRootError error = (GitRootError)o; + + if (!myRoot.equals(error.myRoot)) return false; + if (myType != error.myType) return false; + + return true; + } + + @Override + public int hashCode() { + int result = myType != null ? myType.hashCode() : 0; + result = 31 * result + myRoot.hashCode(); + return result; + } +} diff --git a/plugins/git4idea/src/git4idea/roots/GitRootErrorsFinder.java b/plugins/git4idea/src/git4idea/roots/GitRootErrorsFinder.java new file mode 100644 index 000000000000..776d7cfe2d81 --- /dev/null +++ b/plugins/git4idea/src/git4idea/roots/GitRootErrorsFinder.java @@ -0,0 +1,62 @@ +/* + * Copyright 2000-2012 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 git4idea.roots; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.ProjectLevelVcsManager; +import com.intellij.openapi.vfs.VirtualFile; +import git4idea.PlatformFacade; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; + +/** + * Detects actual Git roots and compares them to the ones registered in Settings | Version Control. + * + * @author Kirill Likhodedov + */ +class GitRootErrorsFinder { + + @NotNull private final Project myProject; + private final PlatformFacade myPlatformFacade; + + GitRootErrorsFinder(@NotNull Project project, @NotNull PlatformFacade platformFacade) { + myProject = project; + myPlatformFacade = platformFacade; + } + + @NotNull + Collection find() { + ProjectLevelVcsManager vcsManager = myPlatformFacade.getVcsManager(myProject); + Collection vcsRoots = Arrays.asList(vcsManager.getRootsUnderVcs(myPlatformFacade.getVcs(myProject))); + Collection gitRoots = new GitRootDetector(myProject).detect().getRoots(); + Collection errors = new ArrayList(); + for (VirtualFile vcsRoot : vcsRoots) { + if (!gitRoots.contains(vcsRoot)) { + errors.add(new GitRootError(GitRootError.Type.EXTRA_ROOT, vcsRoot)); + } + } + for (VirtualFile gitRoot : gitRoots) { + if (!vcsRoots.contains(gitRoot)) { + errors.add(new GitRootError(GitRootError.Type.UNREGISTERED_ROOT, gitRoot)); + } + } + return errors; + } + +} diff --git a/plugins/git4idea/tests/git4idea/roots/AbstractGitRootTest.groovy b/plugins/git4idea/tests/git4idea/roots/AbstractGitRootTest.groovy new file mode 100644 index 000000000000..61a9e0dde4e1 --- /dev/null +++ b/plugins/git4idea/tests/git4idea/roots/AbstractGitRootTest.groovy @@ -0,0 +1,88 @@ +/* + * Copyright 2000-2012 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 git4idea.roots + +import git4idea.test.GitMockVirtualFile +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.io.FileUtil + +/** + * + * @author Kirill Likhodedov + */ +class AbstractGitRootTest { + + /** + * Creates the necessary temporary directories in the filesystem with empty ".git" directories for given roots. + * And creates an instance of the project. + * @param gitRoots path to actual .git roots, relative to the project dir. + */ + Project initProject(Collection gitRoots, Collection projectStructure) { + String projectDir = createDirs(gitRoots) + Project project = [ + getBaseDir: { new GitMockVirtualFile(projectDir) } + ] as Project + createProjectStructure(project, projectStructure); + project + } + + void createProjectStructure(Project project, Collection paths) { + paths.each { String path -> + File file = new File(project.baseDir.path + "/" + path) + file.mkdir() + } + } + + /** + * @return path to the project + */ + private static String createDirs(Collection gitRoots) { + if (gitRoots.empty) { + return FileUtil.createTempDirectory("grdt", null); + } + + File baseDir = createBaseTempDir() + int maxDepth = findMaxDepthAboveProject(gitRoots) + File projectDir = createChild(baseDir, maxDepth) + gitRoots.each { String path -> + File file = new File(projectDir.path + "/" + path) + file.mkdirs() + file.deleteOnExit() + + File gitDir = new File(file, ".git") + gitDir.mkdir() + gitDir.deleteOnExit() + } + return projectDir.path + } + + private static File createBaseTempDir() { + FileUtil.createTempDirectory("pref", null) + } + + private static File createChild(File base, int depth) { + File dir = base + depth.times { dir = FileUtil.createTempDirectory(dir, "grdt", null)} + dir + } + + // Assuming that there are no ".." inside the path - only in the beginning + static int findMaxDepthAboveProject(Collection paths) { + def len = { String path -> path.split("/").count("..") } + len(paths.max(len)) + } + +} diff --git a/plugins/git4idea/tests/git4idea/roots/GitRootDetectorTest.groovy b/plugins/git4idea/tests/git4idea/roots/GitRootDetectorTest.groovy index da95ee304427..48e5c0268436 100644 --- a/plugins/git4idea/tests/git4idea/roots/GitRootDetectorTest.groovy +++ b/plugins/git4idea/tests/git4idea/roots/GitRootDetectorTest.groovy @@ -16,20 +16,18 @@ package git4idea.roots import com.intellij.openapi.project.Project -import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.vfs.VirtualFile -import git4idea.test.GitMockVirtualFile +import git4idea.test.GitGTestUtil import org.junit.Before import org.junit.Test import static org.junit.Assert.assertEquals import static org.junit.Assert.fail -import git4idea.test.GitGTestUtil /** * @author Kirill Likhodedov */ -class GitRootDetectorTest { +class GitRootDetectorTest extends AbstractGitRootTest { Project myProject @@ -126,7 +124,7 @@ class GitRootDetectorTest { * @param map */ private void doTest(Map map) { - initProject(map.gits, map.project) + myProject = initProject(map.gits, map.project) testInfo empty: map.expected.empty, full : map.full, roots: map.expected, @@ -144,65 +142,6 @@ class GitRootDetectorTest { below: false } - /** - * Creates the necessary temporary directories in the filesystem with empty ".git" directories for given roots. - * And creates an instance of the project. - * @param gitRoots path to actual .git roots, relative to the project dir. - */ - private void initProject(Collection gitRoots, Collection projectStructure) { - String projectDir = createDirs(gitRoots) - myProject = [ - getBaseDir: { new GitMockVirtualFile(projectDir) } - ] as Project - createProjectStructure(projectStructure); - } - - void createProjectStructure(Collection paths) { - paths.each { String path -> - File file = new File(myProject.baseDir.path + "/" + path) - file.mkdir() - } - } - - /** - * @return path to the project - */ - private static String createDirs(Collection gitRoots) { - if (gitRoots.empty) { - return FileUtil.createTempDirectory("grdt", null); - } - - File baseDir = createBaseTempDir() - int maxDepth = findMaxDepthAboveProject(gitRoots) - File projectDir = createChild(baseDir, maxDepth) - gitRoots.each { String path -> - File file = new File(projectDir.path + "/" + path) - file.mkdirs() - file.deleteOnExit() - - File gitDir = new File(file, ".git") - gitDir.mkdir() - gitDir.deleteOnExit() - } - return projectDir.path - } - - private static File createBaseTempDir() { - FileUtil.createTempDirectory("pref", null) - } - - private static File createChild(File base, int depth) { - File dir = base - depth.times { dir = FileUtil.createTempDirectory(dir, "grdt", null)} - dir - } - - // Assuming that there are no ".." inside the path - only in the beginning - static int findMaxDepthAboveProject(Collection paths) { - def len = { String path -> path.split("/").count("..") } - len(paths.max(len)) - } - void testInfo(Map expected) { assertInfo(expected, detect()) } diff --git a/plugins/git4idea/tests/git4idea/roots/GitRootErrorsFinderTest.groovy b/plugins/git4idea/tests/git4idea/roots/GitRootErrorsFinderTest.groovy new file mode 100644 index 000000000000..d3e211a246c8 --- /dev/null +++ b/plugins/git4idea/tests/git4idea/roots/GitRootErrorsFinderTest.groovy @@ -0,0 +1,136 @@ +/* + * Copyright 2000-2012 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 git4idea.roots + +import com.intellij.openapi.project.Project +import git4idea.test.GitMockVcsManager +import git4idea.test.GitTestPlatformFacade +import org.junit.Before +import org.junit.Test + +import static git4idea.test.GitGTestUtil.toAbsolute +import static junit.framework.Assert.assertEquals +import git4idea.test.GitMockVirtualFile + +/** + * + * @author Kirill Likhodedov + */ +class GitRootErrorsFinderTest extends AbstractGitRootTest { + + Project myProject + GitTestPlatformFacade myPlatformFacade + GitMockVcsManager myVcsManager + + @Before + void setUp() { + myPlatformFacade = new GitTestPlatformFacade() + } + + @Test + void "No roots => no errors"() { + doTest git: [], + roots: [], + errors: [] + } + + @Test + void "Same 1 root in both => no errors"() { + doTest git: ["."], + roots: ["."], + errors: [] + } + + @Test + void "Same 2 roots in both => no errors"() { + doTest git: ["..", "community"], + roots: ["..", "community"], + errors: [] + } + + @Test + void "One git, no VCS roots => error"() { + doTest git: ["."], + roots: [], + errors: [unreg : ["."]] + } + + @Test + void "One VCS root, no gits => error"() { + doTest git: [], + roots: ["."], + errors: [extra: ["."]] + } + + @Test + void "One root, but different => 2 errors"() { + doTest git: ["."], + roots: ["community"], + errors: [unreg: ["."], extra: ["community"]] + } + + @Test + void "Two roots, one matching, one different => 2 errors"() { + doTest git: [".", "community"], + roots: [".", "contrib"], + errors: [unreg: ["community"], extra: ["contrib"]] + } + + @Test + void "Two roots in git, one matching in VCS => error"() { + doTest git: [".", "community"], + roots: ["."], + errors: [unreg: ["community"]] + } + + @Test + void "Two roots, both not matching => 4 errors"() { + doTest git: ["..", "community"], + roots: [".", "contrib"], + errors: [unreg: ["..", "community"], extra: [".", "contrib"]] + } + + private void doTest(Map map) { + myProject = initProject(map.git, []) + myVcsManager = (GitMockVcsManager) myPlatformFacade.getVcsManager(myProject) + + addVcsRoots(map.roots) + + Collection expected = new ArrayList(); + expected.addAll map.errors.unreg.collect { unreg(it) } + expected.addAll map.errors.extra.collect { extra(it) } + + Collection actual = new GitRootErrorsFinder(myProject, myPlatformFacade).find() + assertEquals expected.toSet(), actual.toSet() + } + + void addVcsRoots(Collection relativeRoots) { + relativeRoots.each { + String root = toAbsolute(it, myProject) + myVcsManager.addRoots(root) + } + } + + GitRootError unreg(String path) { + return new GitRootError(GitRootError.Type.UNREGISTERED_ROOT, new GitMockVirtualFile(toAbsolute(path, myProject))) + } + + GitRootError extra(String path) { + return new GitRootError(GitRootError.Type.EXTRA_ROOT, new GitMockVirtualFile(toAbsolute(path, myProject))) + } + + +} diff --git a/plugins/git4idea/tests/git4idea/test/GitMockVcsManager.groovy b/plugins/git4idea/tests/git4idea/test/GitMockVcsManager.groovy index d9c217ba410b..fd056f5bea8b 100644 --- a/plugins/git4idea/tests/git4idea/test/GitMockVcsManager.groovy +++ b/plugins/git4idea/tests/git4idea/test/GitMockVcsManager.groovy @@ -34,6 +34,10 @@ public class GitMockVcsManager extends ProjectLevelVcsManager { Collection myRoots = [] + void addRoots(String... roots) { + roots.each { myRoots << it } + } + @Override VirtualFile[] getRootsUnderVcs(AbstractVcs vcs) { myRoots.collect { new GitMockVirtualFile(it) } diff --git a/plugins/git4idea/tests/git4idea/test/GitMockVirtualFile.groovy b/plugins/git4idea/tests/git4idea/test/GitMockVirtualFile.groovy index b04c9f840075..7d4ce19d5f55 100644 --- a/plugins/git4idea/tests/git4idea/test/GitMockVirtualFile.groovy +++ b/plugins/git4idea/tests/git4idea/test/GitMockVirtualFile.groovy @@ -122,4 +122,15 @@ class GitMockVirtualFile extends VirtualFile { myPath; } + boolean equals(o) { + if (this.is(o)) return true + if (getClass() != o.class) return false + GitMockVirtualFile file = (GitMockVirtualFile)o + if (myPath != file.myPath) return false + return true + } + + int hashCode() { + return (myPath != null ? myPath.hashCode() : 0) + } } From 652d9225ea8dfeea804d6f211533706c5c6a97d6 Mon Sep 17 00:00:00 2001 From: Kirill Likhodedov Date: Fri, 2 Mar 2012 12:48:44 +0400 Subject: [PATCH 03/13] fix test: don't use ">" symbol in test names --- .../git4idea/roots/GitIntegrationEnablerTest.groovy | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/git4idea/tests/git4idea/roots/GitIntegrationEnablerTest.groovy b/plugins/git4idea/tests/git4idea/roots/GitIntegrationEnablerTest.groovy index 984e70f1f778..73281fea78ca 100644 --- a/plugins/git4idea/tests/git4idea/roots/GitIntegrationEnablerTest.groovy +++ b/plugins/git4idea/tests/git4idea/roots/GitIntegrationEnablerTest.groovy @@ -58,13 +58,13 @@ class GitIntegrationEnablerTest { } @Test - void "Simplest case - 1 root for the whole project => just add VCS root"() { + void "1 root for the whole project, then just add VCS root"() { doTest given(["."]), expect(git_init: []) } @Test - void "No Git roots => init & notify"() { + void "No Git roots, then init & notify"() { doTest given( [], false ), expect( git_init: ["."], vcs_roots: ["."], @@ -72,28 +72,28 @@ class GitIntegrationEnablerTest { } @Test - void "Below Git, no inside => notify"() { + void "Below Git, no inside, then notify"() { doTest given([".."], true, true), expect( git_init: [], notification("Added Git root: ${path("..")}")) } @Test - void "Git for project, some inside => notify"() { + void "Git for project, some inside, then notify"() { doTest given( [".", "community"]), expect(git_init: [], notification("Added Git roots: ${path(".")}, ${path("community")}")) } @Test - void "Below Git, some inside => notify"() { + void "Below Git, some inside, then notify"() { doTest given(["..", "community"], true, true), expect(git_init: [], notification("Added Git roots: ${path("..")}, ${path("community")}")) } @Test - void "Not under Git, some inside => notify"() { + void "Not under Git, some inside, then notify"() { doTest given( ["community", "contrib"], false, false), expect(git_init: [], notification("Added Git roots: ${path("community")}, ${path("contrib")}")) From b2d7e8f1eb9ad15dd0c01aa07e87021429211259 Mon Sep 17 00:00:00 2001 From: Kirill Likhodedov Date: Fri, 2 Mar 2012 13:19:49 +0400 Subject: [PATCH 04/13] GitUpdateProcess: don't restore local changes in the case of update error --- plugins/git4idea/src/git4idea/update/GitUpdateProcess.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/git4idea/src/git4idea/update/GitUpdateProcess.java b/plugins/git4idea/src/git4idea/update/GitUpdateProcess.java index 9e0209a15d0a..848ae5f14802 100644 --- a/plugins/git4idea/src/git4idea/update/GitUpdateProcess.java +++ b/plugins/git4idea/src/git4idea/update/GitUpdateProcess.java @@ -193,11 +193,12 @@ public class GitUpdateProcess { notifyImportantError(myProject, "Error updating " + rootName, "Updating " + rootName + " failed with an error: " + e.getLocalizedMessage()); } finally { - if (!incomplete) { - restoreLocalChanges(context); - } else { + if (incomplete || !success) { mySaver.notifyLocalChangesAreNotRestored(); } + else { + restoreLocalChanges(context); + } } return success; } From e6e20e56bf50116e1c5a87b3dc3fab6a16879487 Mon Sep 17 00:00:00 2001 From: Kirill Likhodedov Date: Fri, 2 Mar 2012 13:21:12 +0400 Subject: [PATCH 05/13] GitMergeUpdater: better logging --- .../src/git4idea/update/GitMergeUpdater.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/plugins/git4idea/src/git4idea/update/GitMergeUpdater.java b/plugins/git4idea/src/git4idea/update/GitMergeUpdater.java index 0cef94988ced..a95da045a147 100644 --- a/plugins/git4idea/src/git4idea/update/GitMergeUpdater.java +++ b/plugins/git4idea/src/git4idea/update/GitMergeUpdater.java @@ -107,13 +107,15 @@ public class GitMergeUpdater extends GitUpdater { final GitMerger merger, GitLineHandler mergeHandler) { final MergeError error = mergeLineListener.getMergeError(); - LOG.info("doUpdate merge error: " + error); + LOG.info("merge error: " + error); if (error == MergeError.CONFLICT) { + LOG.info("Conflict detected"); final boolean allMerged = new MyConflictResolver(myProject, merger, myRoot).merge(); return allMerged ? GitUpdateResult.SUCCESS : GitUpdateResult.INCOMPLETE; } else if (error == MergeError.LOCAL_CHANGES) { + LOG.info("Local changes would be overwritten by merge"); final List paths = getFilesOverwrittenByMerge(mergeLineListener.getOutput()); final Collection changes = getLocalChangesFilteredByFiles(paths); final ChangeListViewerDialog dialog = new ChangeListViewerDialog(myProject, changes, false) { @@ -129,12 +131,16 @@ public class GitMergeUpdater extends GitUpdater { } }); return GitUpdateResult.ERROR; - } else if (untrackedFilesWouldBeOverwrittenByMergeDetector.wasMessageDetected()) { + } + else if (untrackedFilesWouldBeOverwrittenByMergeDetector.wasMessageDetected()) { LOG.info("handleMergeFailure: untracked files would be overwritten by merge"); UntrackedFilesNotifier.notifyUntrackedFilesOverwrittenBy(myProject, untrackedFilesWouldBeOverwrittenByMergeDetector.getFiles(), "merge"); return GitUpdateResult.ERROR; - } else { - GitUIUtil.notifyImportantError(myProject, "Error merging", GitUIUtil.stringifyErrors(mergeHandler.errors())); + } + else { + String errors = GitUIUtil.stringifyErrors(mergeHandler.errors()); + LOG.info("Unknown error: " + errors); + GitUIUtil.notifyImportantError(myProject, "Error merging", errors); return GitUpdateResult.ERROR; } } From 8cfb1979159d126a0110cfe184134200823f49c1 Mon Sep 17 00:00:00 2001 From: "Denis.Zhdanov" Date: Thu, 1 Mar 2012 16:34:23 +0400 Subject: [PATCH 06/13] IDEA-76142: Gradle support - cannot update IDEA projects once one of build.gradle files changes Floating toolbar is shown for the 'sync project structures' nodes now --- .../openapi/actionSystem/ActionToolbar.java | 4 +- .../actionSystem/impl/ActionToolbarImpl.java | 5 + .../AbstractGradleSyncTreeNodeAction.java | 107 ++++++++++++++++++ .../action/GradleImportEntityAction.java | 48 +++----- .../action/GradleRefreshProjectAction.java | 2 +- .../GradleProjectStructureChangesPanel.java | 92 ++++++++++++++- .../plugins/gradle/ui/GradleDataKeys.java | 7 +- .../plugins/gradle/util/GradleConstants.java | 5 +- 8 files changed, 229 insertions(+), 41 deletions(-) create mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/action/AbstractGradleSyncTreeNodeAction.java diff --git a/platform/platform-api/src/com/intellij/openapi/actionSystem/ActionToolbar.java b/platform/platform-api/src/com/intellij/openapi/actionSystem/ActionToolbar.java index 39df16d826d0..5b98bd2bef9c 100644 --- a/platform/platform-api/src/com/intellij/openapi/actionSystem/ActionToolbar.java +++ b/platform/platform-api/src/com/intellij/openapi/actionSystem/ActionToolbar.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2011 JetBrains s.r.o. + * Copyright 2000-2012 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. @@ -104,6 +104,8 @@ public interface ActionToolbar extends SwitchProvider, QuickActionProvider { */ void updateActionsImmediately(); + boolean hasVisibleActions(); + /** * @param component will be used for datacontext computations */ diff --git a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionToolbarImpl.java b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionToolbarImpl.java index a18a9370e810..3c6e4d073564 100644 --- a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionToolbarImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionToolbarImpl.java @@ -870,6 +870,11 @@ public class ActionToolbarImpl extends JPanel implements ActionToolbar { } } + @Override + public boolean hasVisibleActions() { + return !myVisibleActions.isEmpty(); + } + public void setTargetComponent(final JComponent component) { myTargetComponent = component; diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/action/AbstractGradleSyncTreeNodeAction.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/action/AbstractGradleSyncTreeNodeAction.java new file mode 100644 index 000000000000..70f661438b4d --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/action/AbstractGradleSyncTreeNodeAction.java @@ -0,0 +1,107 @@ +package org.jetbrains.plugins.gradle.action; + +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.project.Project; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.gradle.ui.GradleDataKeys; +import org.jetbrains.plugins.gradle.ui.GradleProjectStructureNode; +import org.jetbrains.plugins.gradle.util.GradleConstants; + +import java.util.*; + +/** + * Common super class for actions that are invoked on 'sync project structures' tree nodes. + *

+ * Not thread-safe. + * + * @author Denis Zhdanov + * @since 2/29/12 4:15 PM + */ +public abstract class AbstractGradleSyncTreeNodeAction extends AnAction { + + private static final Map HELPERS = new HashMap(); + static { + HELPERS.put(GradleConstants.SYNC_TREE_CONTEXT_MENU_PLACE, new ContextMenuHelper()); + HELPERS.put(GradleConstants.SYNC_TREE_FLOATING_TOOLBAR_PLACE, new FloatingToolbarHelper()); + } + + @SuppressWarnings("unchecked") + @Override + public void update(AnActionEvent e) { + final Project project = PlatformDataKeys.PROJECT.getData(e.getDataContext()); + final Helper helper = HELPERS.get(e.getPlace()); + if (project == null || helper == null) { + e.getPresentation().setEnabled(false); + e.getPresentation().setVisible(false); + return; + } + Collection> nodes = helper.getTargetNodes(e); + if (nodes != null) { + filterNodes(nodes); + } + helper.updatePresentation(nodes, e.getPresentation()); + } + + @Override + public void actionPerformed(AnActionEvent e) { + final Project project = PlatformDataKeys.PROJECT.getData(e.getDataContext()); + final Helper helper = HELPERS.get(e.getPlace()); + if (project == null || helper == null) { + return; + } + final Collection> nodes = helper.getTargetNodes(e); + if (nodes == null || nodes.isEmpty()) { + return; + } + doActionPerformed(nodes, project); + } + + protected abstract void doActionPerformed(@NotNull Collection> nodes, @NotNull Project project); + + protected void filterNodes(@NotNull Collection> nodes) { + } + + private interface Helper { + + @Nullable + Collection> getTargetNodes(@NotNull AnActionEvent e); + + void updatePresentation(@Nullable Collection> nodes, @NotNull Presentation presentation); + } + + private static class ContextMenuHelper implements Helper { + @Nullable + @Override + public Collection> getTargetNodes(@NotNull AnActionEvent e) { + return GradleDataKeys.SYNC_TREE_SELECTED_NODE.getData(e.getDataContext()); + } + + @Override + public void updatePresentation(@Nullable Collection> nodes, @NotNull Presentation presentation) { + presentation.setVisible(true); + presentation.setEnabled(nodes != null && !nodes.isEmpty()); + } + } + + private static class FloatingToolbarHelper implements Helper { + @SuppressWarnings("unchecked") + @Nullable + @Override + public Collection> getTargetNodes(@NotNull AnActionEvent e) { + List> result = new ArrayList>(); + final GradleProjectStructureNode node = GradleDataKeys.SYNC_TREE_NODE_UNDER_MOUSE.getData(e.getDataContext()); + if (node != null) { + result.add(node); + } + return result; + } + + @Override + public void updatePresentation(@Nullable Collection> nodes, @NotNull Presentation presentation) { + boolean active = nodes != null && !nodes.isEmpty(); + presentation.setVisible(active); + presentation.setEnabled(active); + } + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/action/GradleImportEntityAction.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/action/GradleImportEntityAction.java index 9e9920e17cba..2d4f047e1a23 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/action/GradleImportEntityAction.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/action/GradleImportEntityAction.java @@ -1,21 +1,15 @@ package org.jetbrains.plugins.gradle.action; -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.DataContext; -import com.intellij.openapi.actionSystem.PlatformDataKeys; -import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.gradle.config.GradleTextAttributes; import org.jetbrains.plugins.gradle.importing.GradleLocalNodeImportHelper; -import org.jetbrains.plugins.gradle.ui.GradleDataKeys; import org.jetbrains.plugins.gradle.ui.GradleProjectStructureNode; import org.jetbrains.plugins.gradle.util.GradleBundle; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; +import java.util.Iterator; import java.util.List; /** @@ -26,46 +20,32 @@ import java.util.List; * @author Denis Zhdanov * @since 2/7/12 10:32 AM */ -public class GradleImportEntityAction extends AnAction { +public class GradleImportEntityAction extends AbstractGradleSyncTreeNodeAction { - private static final Logger LOG = Logger.getInstance("#" + GradleImportEntityAction.class.getName()); - public GradleImportEntityAction() { getTemplatePresentation().setText(GradleBundle.message("gradle.action.import.entity.text")); getTemplatePresentation().setDescription(GradleBundle.message("gradle.action.import.entity.description")); } @Override - public void update(AnActionEvent e) { - final Collection> nodes = getInterestedNodes(e.getDataContext()); - e.getPresentation().setEnabled(!nodes.isEmpty()); + protected void filterNodes(@NotNull Collection> nodes) { + for (Iterator> iterator = nodes.iterator(); iterator.hasNext(); ) { + GradleProjectStructureNode node = iterator.next(); + if (node.getDescriptor().getAttributes() != GradleTextAttributes.GRADLE_LOCAL_CHANGE) { + iterator.remove(); + } + } } @Override - public void actionPerformed(AnActionEvent e) { - final Project project = PlatformDataKeys.PROJECT.getData(e.getDataContext()); - if (project == null) { - LOG.warn("Can't import gradle-local entities. Reason: target intellij project is undefined"); - return; - } - - final Collection> nodes = getInterestedNodes(e.getDataContext()); + protected void doActionPerformed(@NotNull Collection> nodes, @NotNull Project project) { final GradleLocalNodeImportHelper importHelper = project.getComponent(GradleLocalNodeImportHelper.class); - importHelper.importNodes(nodes); - } - - @NotNull - private static Collection> getInterestedNodes(@NotNull DataContext context) { - final Collection> selectedNodes = GradleDataKeys.SYNC_TREE_NODE.getData(context); - if (selectedNodes == null) { - return Collections.emptyList(); - } - List> result = new ArrayList>(); - for (GradleProjectStructureNode node : selectedNodes) { + final List> interestedNodes = new ArrayList>(); + for (GradleProjectStructureNode node : nodes) { if (node.getDescriptor().getAttributes() == GradleTextAttributes.GRADLE_LOCAL_CHANGE) { - result.add(node); + interestedNodes.add(node); } } - return result; + importHelper.importNodes(interestedNodes); } } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/action/GradleRefreshProjectAction.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/action/GradleRefreshProjectAction.java index 087e1706733b..feb2835de739 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/action/GradleRefreshProjectAction.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/action/GradleRefreshProjectAction.java @@ -39,6 +39,6 @@ public class GradleRefreshProjectAction extends AbstractGradleLinkedProjectActio @Override protected void doActionPerformed(@NotNull final Project project, @NotNull final String linkedProjectPath) { - GradleUtil.refreshProject(project); + GradleUtil.refreshProject(project); } } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesPanel.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesPanel.java index eff339847cd4..447b91d7dca0 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesPanel.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesPanel.java @@ -1,7 +1,13 @@ package org.jetbrains.plugins.gradle.sync; import com.intellij.ide.ui.customization.CustomizationUtil; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.actionSystem.ex.AnActionListener; import com.intellij.openapi.project.Project; +import com.intellij.ui.ColoredSideBorder; +import com.intellij.ui.HintHint; +import com.intellij.ui.HintListener; +import com.intellij.ui.LightweightHint; import com.intellij.ui.treeStructure.Tree; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.ui.UIUtil; @@ -19,8 +25,10 @@ import javax.swing.*; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreePath; import java.awt.*; +import java.awt.event.*; import java.util.ArrayList; import java.util.Collection; +import java.util.EventObject; import java.util.List; /** @@ -34,6 +42,8 @@ public class GradleProjectStructureChangesPanel extends GradleToolWindowPanel { private Tree myTree; private GradleProjectStructureTreeModel myTreeModel; private GradleProjectStructureContext myContext; + private Object myNodeUnderMouse; + private LightweightHint myHint; public GradleProjectStructureChangesPanel(@NotNull Project project, @NotNull GradleProjectStructureContext context) { super(project, GradleConstants.TOOL_WINDOW_TOOLBAR_PLACE); @@ -75,7 +85,8 @@ public class GradleProjectStructureChangesPanel extends GradleToolWindowPanel { result.add(myTree, constraints); result.setBackground(myTree.getBackground()); - CustomizationUtil.installPopupHandler(myTree, GradleConstants.ACTION_GROUP_SYNC_TREE, GradleConstants.SYNC_TREE_PLACE); + CustomizationUtil.installPopupHandler(myTree, GradleConstants.ACTION_GROUP_SYNC_TREE, GradleConstants.SYNC_TREE_CONTEXT_MENU_PLACE); + setupActionHint(); return result; } @@ -85,10 +96,84 @@ public class GradleProjectStructureChangesPanel extends GradleToolWindowPanel { int i = 1; } + private void setupActionHint() { + final ActionManager manager = ActionManager.getInstance(); + manager.addAnActionListener(new AnActionListener.Adapter() { + @Override + public void afterActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) { + if (event != null && GradleConstants.SYNC_TREE_FLOATING_TOOLBAR_PLACE.equals(event.getPlace())) { + hideFloatingToolbar(); + } + } + }, getProject()); + + final ActionGroup actionGroup = (ActionGroup)manager.getAction(GradleConstants.ACTION_GROUP_SYNC_TREE); + final ActionToolbar toolbar = manager.createActionToolbar(GradleConstants.SYNC_TREE_FLOATING_TOOLBAR_PLACE, actionGroup, true); + toolbar.setTargetComponent(this); + final JComponent toolbarComponent = toolbar.getComponent(); + toolbarComponent.setOpaque(true); + final Color foreground = myTree.getForeground(); + toolbarComponent.setForeground(foreground); + toolbarComponent.setBackground(myTree.getBackground()); + toolbarComponent.setBorder(new ColoredSideBorder(foreground, foreground, foreground, foreground, 1)); + + myTree.addMouseMotionListener(new MouseMotionAdapter() { + + Object activeNode; + + @Override + public void mouseMoved(MouseEvent e) { + final TreePath path = myTree.getPathForLocation(e.getX(), e.getY()); + if (path == null) { + return; + } + final Object node = path.getLastPathComponent(); + myNodeUnderMouse = node; + LightweightHint hint = myHint; + if (node == activeNode && hint.isVisible()) { + return; + } + hideFloatingToolbar(); + toolbar.updateActionsImmediately(); + if (!toolbar.hasVisibleActions()) { + activeNode = null; + return; + } + activeNode = node; + final LightweightHint lightweightHint = new LightweightHint(toolbarComponent); + lightweightHint.addHintListener(new HintListener() { + @Override + public void hintHidden(EventObject event) { + activeNode = null; + } + }); + final Rectangle bounds = myTree.getPathBounds(path); + if (bounds == null) { + assert false; + return; + } + final Icon icon = ((GradleProjectStructureNode)node).getDescriptor().getOpenIcon(); + int xAdjustment = 0; + if (icon != null) { + xAdjustment = icon.getIconWidth(); + } + lightweightHint.show(myTree, bounds.x + xAdjustment, bounds.y + bounds.height, myTree, new HintHint(e)); + myHint = lightweightHint; + } + }); + } + + private void hideFloatingToolbar() { + final LightweightHint hint = myHint; + if (hint != null && hint.isVisible()) { + hint.hide(); + } + } + @Nullable @Override public Object getData(@NonNls String dataId) { - if (GradleDataKeys.SYNC_TREE_NODE.is(dataId)) { + if (GradleDataKeys.SYNC_TREE_SELECTED_NODE.is(dataId)) { TreePath[] paths = myTree.getSelectionPaths(); if (paths == null) { return null; @@ -99,6 +184,9 @@ public class GradleProjectStructureChangesPanel extends GradleToolWindowPanel { } return result; } + else if (GradleDataKeys.SYNC_TREE_NODE_UNDER_MOUSE.is(dataId)) { + return myNodeUnderMouse; + } else { return super.getData(dataId); } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/ui/GradleDataKeys.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/ui/GradleDataKeys.java index 134db91a6efc..7578d997c108 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/ui/GradleDataKeys.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/ui/GradleDataKeys.java @@ -11,7 +11,12 @@ import java.util.Collection; public class GradleDataKeys { /** Key for obtaining currently selected nodes at the gradle 'sync project structure' tree. */ - public static final DataKey>> SYNC_TREE_NODE = DataKey.create("gradle.sync.tree.node"); + public static final DataKey>> SYNC_TREE_SELECTED_NODE + = DataKey.create("gradle.sync.tree.node.selected"); + + /** Key for obtaining node under mouse cursor at the gradle 'sync project structure' tree. */ + public static final DataKey> SYNC_TREE_NODE_UNDER_MOUSE + = DataKey.create("gradle.sync.tree.node.under.mouse"); private GradleDataKeys() { } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/util/GradleConstants.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/util/GradleConstants.java index ebb7978947da..46988a06a5a7 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/util/GradleConstants.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/util/GradleConstants.java @@ -16,8 +16,9 @@ public class GradleConstants { @NonNls public static final String EXTENSION = "gradle"; @NonNls public static final String DEFAULT_SCRIPT_NAME = "build.gradle"; - @NonNls public static final String TOOL_WINDOW_TOOLBAR_PLACE = "GRADLE_SYNC_CHANGES_TOOLBAR"; - @NonNls public static final String SYNC_TREE_PLACE = "GRADLE_SYNC_TREE_PLACE"; + @NonNls public static final String TOOL_WINDOW_TOOLBAR_PLACE = "GRADLE_SYNC_CHANGES_TOOLBAR"; + @NonNls public static final String SYNC_TREE_CONTEXT_MENU_PLACE = "GRADLE_SYNC_TREE_CONTEXT_MENU_PLACE"; + @NonNls public static final String SYNC_TREE_FLOATING_TOOLBAR_PLACE = "GRADLE_SYNC_TREE_FLOATING_TOOLBAR_PLACE"; @NonNls public static final String ACTION_GROUP_SYNC_TREE = "Gradle.SyncTreeGroup"; From ceb9cb4c4dc9ff5968cacee2c07d6ec069675727 Mon Sep 17 00:00:00 2001 From: "Denis.Zhdanov" Date: Thu, 1 Mar 2012 16:52:54 +0400 Subject: [PATCH 07/13] IDEA-76142: Gradle support - cannot update IDEA projects once one of build.gradle files changes Reduced delay for automatic gradle project refresh triggering --- .../gradle/sync/GradleProjectStructureChangesDetector.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesDetector.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesDetector.java index b948571593d8..f53468a35f89 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesDetector.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesDetector.java @@ -29,7 +29,7 @@ import java.util.concurrent.atomic.AtomicLong; */ public class GradleProjectStructureChangesDetector extends AbstractProjectComponent { - private static final int REFRESH_DELAY_MILLIS = (int)TimeUnit.SECONDS.toMillis(2); + private static final int REFRESH_DELAY_MILLIS = (int)500; private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD); private final AtomicLong myStartRefreshTime = new AtomicLong(); @@ -78,7 +78,7 @@ public class GradleProjectStructureChangesDetector extends AbstractProjectCompon } myStartRefreshTime.set(System.currentTimeMillis() + REFRESH_DELAY_MILLIS); myAlarm.cancelAllRequests(); - myAlarm.addRequest(myRequest, REFRESH_DELAY_MILLIS + 16); + myAlarm.addRequest(myRequest, REFRESH_DELAY_MILLIS); } private class RefreshRequest implements Runnable { From 0a539658121da5fbd2ef8508177ddd68355138ec Mon Sep 17 00:00:00 2001 From: "Denis.Zhdanov" Date: Fri, 2 Mar 2012 13:02:46 +0400 Subject: [PATCH 08/13] IDEA-76142: Gradle support - cannot update IDEA projects once one of build.gradle files changes Hide node toolbar if the mouse goes out of the tree --- .../GradleProjectStructureChangesPanel.java | 36 ++++++++++++++++--- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesPanel.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesPanel.java index 447b91d7dca0..d7653cb6eec0 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesPanel.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesPanel.java @@ -9,6 +9,7 @@ import com.intellij.ui.HintHint; import com.intellij.ui.HintListener; import com.intellij.ui.LightweightHint; import com.intellij.ui.treeStructure.Tree; +import com.intellij.util.Alarm; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NonNls; @@ -38,12 +39,14 @@ import java.util.List; * @since 11/3/11 3:58 PM */ public class GradleProjectStructureChangesPanel extends GradleToolWindowPanel { - + + private final Alarm myToolbarAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD); + private Tree myTree; private GradleProjectStructureTreeModel myTreeModel; private GradleProjectStructureContext myContext; private Object myNodeUnderMouse; - private LightweightHint myHint; + private LightweightHint myToolbar; public GradleProjectStructureChangesPanel(@NotNull Project project, @NotNull GradleProjectStructureContext context) { super(project, GradleConstants.TOOL_WINDOW_TOOLBAR_PLACE); @@ -129,7 +132,7 @@ public class GradleProjectStructureChangesPanel extends GradleToolWindowPanel { } final Object node = path.getLastPathComponent(); myNodeUnderMouse = node; - LightweightHint hint = myHint; + LightweightHint hint = myToolbar; if (node == activeNode && hint.isVisible()) { return; } @@ -158,15 +161,38 @@ public class GradleProjectStructureChangesPanel extends GradleToolWindowPanel { xAdjustment = icon.getIconWidth(); } lightweightHint.show(myTree, bounds.x + xAdjustment, bounds.y + bounds.height, myTree, new HintHint(e)); - myHint = lightweightHint; + myToolbar = lightweightHint; + startToolbarTracking(); } }); } + private void startToolbarTracking() { + myToolbarAlarm.cancelAllRequests(); + final int delayMillis = 300; + myToolbarAlarm.addRequest(new Runnable() { + @Override + public void run() { + if (myToolbar == null) { + return; + } + final Point location = MouseInfo.getPointerInfo().getLocation(); + SwingUtilities.convertPointFromScreen(location, GradleProjectStructureChangesPanel.this); + if (GradleProjectStructureChangesPanel.this.contains(location)) { + myToolbarAlarm.addRequest(this, delayMillis); + } + else { + hideFloatingToolbar(); + } + } + }, delayMillis); + } + private void hideFloatingToolbar() { - final LightweightHint hint = myHint; + final LightweightHint hint = myToolbar; if (hint != null && hint.isVisible()) { hint.hide(); + myToolbarAlarm.cancelAllRequests(); } } From e6635c3f1c186e11f21ba6568bf03a6bf5888757 Mon Sep 17 00:00:00 2001 From: "Denis.Zhdanov" Date: Fri, 2 Mar 2012 13:03:14 +0400 Subject: [PATCH 09/13] IDEA-76142: Gradle support - cannot update IDEA projects once one of build.gradle files changes Disable tree node actions when gradle project is being refreshed --- .../action/AbstractGradleSyncTreeNodeAction.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/action/AbstractGradleSyncTreeNodeAction.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/action/AbstractGradleSyncTreeNodeAction.java index 70f661438b4d..1f554a275230 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/action/AbstractGradleSyncTreeNodeAction.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/action/AbstractGradleSyncTreeNodeAction.java @@ -1,9 +1,12 @@ package org.jetbrains.plugins.gradle.action; import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.project.Project; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.gradle.task.GradleTaskManager; +import org.jetbrains.plugins.gradle.task.GradleTaskType; import org.jetbrains.plugins.gradle.ui.GradleDataKeys; import org.jetbrains.plugins.gradle.ui.GradleProjectStructureNode; import org.jetbrains.plugins.gradle.util.GradleConstants; @@ -99,9 +102,15 @@ public abstract class AbstractGradleSyncTreeNodeAction extends AnAction { @Override public void updatePresentation(@Nullable Collection> nodes, @NotNull Presentation presentation) { - boolean active = nodes != null && !nodes.isEmpty(); - presentation.setVisible(active); - presentation.setEnabled(active); + boolean visible = nodes != null && !nodes.isEmpty(); + presentation.setVisible(visible); + + boolean enabled = visible; + if (enabled) { + final GradleTaskManager taskManager = ServiceManager.getService(GradleTaskManager.class); + enabled = taskManager == null || !taskManager.hasTaskOfTypeInProgress(GradleTaskType.RESOLVE_PROJECT); + } + presentation.setEnabled(enabled); } } } From 2c643b63a5c63c62accf41dcd48ab0ad6a293ecf Mon Sep 17 00:00:00 2001 From: "Denis.Zhdanov" Date: Fri, 2 Mar 2012 13:19:28 +0400 Subject: [PATCH 10/13] IDEA-76142: Gradle support - cannot update IDEA projects once one of build.gradle files changes Context menu node action is disabled if gradle project is being refreshed --- .../AbstractGradleSyncTreeNodeAction.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/action/AbstractGradleSyncTreeNodeAction.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/action/AbstractGradleSyncTreeNodeAction.java index 1f554a275230..bd8fd5e98219 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/action/AbstractGradleSyncTreeNodeAction.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/action/AbstractGradleSyncTreeNodeAction.java @@ -44,6 +44,12 @@ public abstract class AbstractGradleSyncTreeNodeAction extends AnAction { filterNodes(nodes); } helper.updatePresentation(nodes, e.getPresentation()); + if (e.getPresentation().isEnabled()) { + final GradleTaskManager taskManager = ServiceManager.getService(GradleTaskManager.class); + if (taskManager != null && taskManager.hasTaskOfTypeInProgress(GradleTaskType.RESOLVE_PROJECT)) { + e.getPresentation().setEnabled(false); + } + } } @Override @@ -102,15 +108,9 @@ public abstract class AbstractGradleSyncTreeNodeAction extends AnAction { @Override public void updatePresentation(@Nullable Collection> nodes, @NotNull Presentation presentation) { - boolean visible = nodes != null && !nodes.isEmpty(); - presentation.setVisible(visible); - - boolean enabled = visible; - if (enabled) { - final GradleTaskManager taskManager = ServiceManager.getService(GradleTaskManager.class); - enabled = taskManager == null || !taskManager.hasTaskOfTypeInProgress(GradleTaskType.RESOLVE_PROJECT); - } - presentation.setEnabled(enabled); + boolean active = nodes != null && !nodes.isEmpty(); + presentation.setVisible(active); + presentation.setEnabled(active); } } } From 02b252d29882816c8aa17213d9c8e042e53123f5 Mon Sep 17 00:00:00 2001 From: "Denis.Zhdanov" Date: Fri, 2 Mar 2012 14:32:34 +0400 Subject: [PATCH 11/13] IDEA-76142: Gradle support - cannot update IDEA projects once one of build.gradle files changes 1. Hide the floating toolbar on context menu opening; 2. Don't show the floating toolbar if context menu is open; --- .../GradleProjectStructureChangesPanel.java | 39 ++++++++++++------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesPanel.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesPanel.java index d7653cb6eec0..95fd8b720c73 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesPanel.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesPanel.java @@ -2,6 +2,7 @@ package org.jetbrains.plugins.gradle.sync; import com.intellij.ide.ui.customization.CustomizationUtil; import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.actionSystem.ex.ActionManagerEx; import com.intellij.openapi.actionSystem.ex.AnActionListener; import com.intellij.openapi.project.Project; import com.intellij.ui.ColoredSideBorder; @@ -46,7 +47,7 @@ public class GradleProjectStructureChangesPanel extends GradleToolWindowPanel { private GradleProjectStructureTreeModel myTreeModel; private GradleProjectStructureContext myContext; private Object myNodeUnderMouse; - private LightweightHint myToolbar; + private LightweightHint myHint; public GradleProjectStructureChangesPanel(@NotNull Project project, @NotNull GradleProjectStructureContext context) { super(project, GradleConstants.TOOL_WINDOW_TOOLBAR_PLACE); @@ -100,18 +101,18 @@ public class GradleProjectStructureChangesPanel extends GradleToolWindowPanel { } private void setupActionHint() { - final ActionManager manager = ActionManager.getInstance(); - manager.addAnActionListener(new AnActionListener.Adapter() { + final ActionManagerEx actionManager = ActionManagerEx.getInstanceEx(); + actionManager.addAnActionListener(new AnActionListener.Adapter() { @Override public void afterActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) { if (event != null && GradleConstants.SYNC_TREE_FLOATING_TOOLBAR_PLACE.equals(event.getPlace())) { - hideFloatingToolbar(); + hideHind(); } } }, getProject()); - final ActionGroup actionGroup = (ActionGroup)manager.getAction(GradleConstants.ACTION_GROUP_SYNC_TREE); - final ActionToolbar toolbar = manager.createActionToolbar(GradleConstants.SYNC_TREE_FLOATING_TOOLBAR_PLACE, actionGroup, true); + final ActionGroup actionGroup = (ActionGroup)actionManager.getAction(GradleConstants.ACTION_GROUP_SYNC_TREE); + final ActionToolbar toolbar = actionManager.createActionToolbar(GradleConstants.SYNC_TREE_FLOATING_TOOLBAR_PLACE, actionGroup, true); toolbar.setTargetComponent(this); final JComponent toolbarComponent = toolbar.getComponent(); toolbarComponent.setOpaque(true); @@ -132,11 +133,14 @@ public class GradleProjectStructureChangesPanel extends GradleToolWindowPanel { } final Object node = path.getLastPathComponent(); myNodeUnderMouse = node; - LightweightHint hint = myToolbar; + LightweightHint hint = myHint; if (node == activeNode && hint.isVisible()) { return; } - hideFloatingToolbar(); + hideHind(); + if (!actionManager.isActionPopupStackEmpty()) { + return; + } toolbar.updateActionsImmediately(); if (!toolbar.hasVisibleActions()) { activeNode = null; @@ -161,10 +165,19 @@ public class GradleProjectStructureChangesPanel extends GradleToolWindowPanel { xAdjustment = icon.getIconWidth(); } lightweightHint.show(myTree, bounds.x + xAdjustment, bounds.y + bounds.height, myTree, new HintHint(e)); - myToolbar = lightweightHint; + myHint = lightweightHint; startToolbarTracking(); } }); + myTree.addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + // Hide hint on context menu opening. + if (e.isPopupTrigger()) { + hideHind(); + } + } + }); } private void startToolbarTracking() { @@ -173,7 +186,7 @@ public class GradleProjectStructureChangesPanel extends GradleToolWindowPanel { myToolbarAlarm.addRequest(new Runnable() { @Override public void run() { - if (myToolbar == null) { + if (myHint == null) { return; } final Point location = MouseInfo.getPointerInfo().getLocation(); @@ -182,14 +195,14 @@ public class GradleProjectStructureChangesPanel extends GradleToolWindowPanel { myToolbarAlarm.addRequest(this, delayMillis); } else { - hideFloatingToolbar(); + hideHind(); } } }, delayMillis); } - private void hideFloatingToolbar() { - final LightweightHint hint = myToolbar; + private void hideHind() { + final LightweightHint hint = myHint; if (hint != null && hint.isVisible()) { hint.hide(); myToolbarAlarm.cancelAllRequests(); From e09389d1aeec482bc4ff7ccfc4ef5745fb3304fc Mon Sep 17 00:00:00 2001 From: "Gregory.Shrago" Date: Fri, 2 Mar 2012 14:32:23 +0400 Subject: [PATCH 12/13] improve trace line parsing & support file-only mode --- .../execution/filters/ExceptionWorker.java | 82 ++++++++----------- 1 file changed, 36 insertions(+), 46 deletions(-) diff --git a/java/openapi/src/com/intellij/execution/filters/ExceptionWorker.java b/java/openapi/src/com/intellij/execution/filters/ExceptionWorker.java index d343b1a83729..4c80ff258045 100644 --- a/java/openapi/src/com/intellij/execution/filters/ExceptionWorker.java +++ b/java/openapi/src/com/intellij/execution/filters/ExceptionWorker.java @@ -28,22 +28,18 @@ import com.intellij.psi.PsiClass; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.PsiShortNamesCache; import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.Nullable; import java.awt.*; /** -* Created by IntelliJ IDEA. -* User: Irina.Chernushina -* Date: 8/5/11 -* Time: 8:36 PM -* To change this template use File | Settings | File Templates. -*/ + * User: Irina.Chernushina + * Date: 8/5/11 + * Time: 8:36 PM + */ public class ExceptionWorker { - @NonNls private static final String AT = "at"; - private static final String AT_PREFIX = AT + " "; private static final TextAttributes HYPERLINK_ATTRIBUTES = EditorColorsManager .getInstance().getGlobalScheme().getAttributes(CodeInsightColors.HYPERLINK_ATTRIBUTES); @@ -54,7 +50,7 @@ public class ExceptionWorker { private PsiClass myClass; private PsiFile myFile; private String myMethod; - private Trinity myInfo; + private Trinity myInfo; public ExceptionWorker(Project project, final GlobalSearchScope searchScope) { myProject = project; @@ -71,31 +67,32 @@ public class ExceptionWorker { myMethod = myInfo.getSecond().substring(line); String className = myInfo.first.substring(line).trim(); final int dollarIndex = className.indexOf('$'); - if (dollarIndex >= 0){ + if (dollarIndex >= 0) { className = className.substring(0, dollarIndex); } final int lparenthIndex = myInfo.third.getStartOffset(); final int rparenthIndex = myInfo.third.getEndOffset(); - final String fileAndLine = line.substring(lparenthIndex + 1, rparenthIndex).trim(); + final String fileAndLine = line.substring(lparenthIndex + 1, rparenthIndex); final int colonIndex = fileAndLine.lastIndexOf(':'); - if (colonIndex < 0) return; + if (colonIndex <= 0) return; - final String lineString = fileAndLine.substring(colonIndex + 1); - try{ + final String lineString = fileAndLine.substring(colonIndex + 1).trim(); + try { final int lineNumber = Integer.parseInt(lineString); final PsiManager manager = PsiManager.getInstance(myProject); final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(manager.getProject()); myClass = psiFacade.findClass(className, mySearchScope); - if (myClass == null) { - myClass = psiFacade.findClass(className, GlobalSearchScope.allScope(myProject)); - /*if (myClass == null) {//try to find class according to all dollars in package name - myClass = psiFacade.findClass(className, GlobalSearchScope.allScope(myProject)); - }*/ - if (myClass == null) return; + myClass = myClass != null ? myClass : psiFacade.findClass(className, GlobalSearchScope.allScope(myProject)); + myFile = myClass == null ? null : (PsiFile)myClass.getContainingFile().getNavigationElement(); + if (myFile == null) { + // try find the file with the required name + PsiFile[] files = PsiShortNamesCache.getInstance(myProject).getFilesByName(fileAndLine.substring(0, colonIndex).trim()); + if (files.length > 0) { + myFile = files[0]; + } } - myFile = (PsiFile) myClass.getContainingFile().getNavigationElement(); if (myFile == null) return; /* @@ -120,7 +117,7 @@ public class ExceptionWorker { } myResult = new Filter.Result(highlightStartOffset, highlightEndOffset, linkInfo, attributes); } - catch(NumberFormatException e){ + catch (NumberFormatException e) { // } } @@ -147,34 +144,27 @@ public class ExceptionWorker { @Nullable static Trinity parseExceptionLine(final String line) { - int atIndex = line.indexOf(AT_PREFIX); - if (atIndex < 0) return null; + int lparenIdx = line.indexOf('('); + int rparenIdx = line.indexOf(')', lparenIdx + 1); + if (lparenIdx < 0 || rparenIdx <= lparenIdx) return null; - final int lparenthIndex = line.indexOf('(', atIndex); - if (lparenthIndex < 0) return null; - final int lastDotIndex = line.lastIndexOf('.', lparenthIndex); - if (lastDotIndex < 0 || lastDotIndex < atIndex) return null; - - final int rparenthIndex = line.indexOf(')', lparenthIndex); - if (rparenthIndex < 0) return null; + int dotIdx = line.lastIndexOf('.', lparenIdx); + int startIdx = handleSpaces(line, dotIdx, -1, false); + if (startIdx == dotIdx) return null; // class, method, link - return Trinity.create(adjustedRange(line, atIndex + AT_PREFIX.length(), lastDotIndex), - adjustedRange(line, lastDotIndex + 1, lparenthIndex), new TextRange(lparenthIndex, rparenthIndex)); + return Trinity.create(new TextRange(startIdx + 1, handleSpaces(line, dotIdx, -1, true)), + new TextRange(handleSpaces(line, dotIdx + 1, 1, true), handleSpaces(line, lparenIdx + 1, -1, true)), + new TextRange(lparenIdx, rparenIdx)); } - private static TextRange adjustedRange(final String line, final int start, final int end) { - String sub = line.substring(start, end); - return new TextRange(start, end - spacesEnd(sub)); - } - - private static int spacesEnd(final String s) { - int cnt = 0; - for (int i = s.length() - 1; i >= 0; i--) { - final char c = s.charAt(i); - if (! Character.isSpaceChar(c)) return cnt; - ++ cnt; + private static int handleSpaces(String line, int pos, int delta, boolean skip) { + int len = line.length(); + while (pos >= 0 && pos < len) { + final char c = line.charAt(pos); + if (skip != Character.isSpaceChar(c)) break; + pos += delta; } - return 0; + return pos; } } From 170c1da49d5b4a0ae37418fe068b94d4fc54a49e Mon Sep 17 00:00:00 2001 From: Eugene Zhuravlev Date: Fri, 2 Mar 2012 14:57:04 +0400 Subject: [PATCH 13/13] fix NPE --- jps/jps-builders/src/org/jetbrains/jps/server/ServerState.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jps/jps-builders/src/org/jetbrains/jps/server/ServerState.java b/jps/jps-builders/src/org/jetbrains/jps/server/ServerState.java index 0dd441fd22ce..40210375c592 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/server/ServerState.java +++ b/jps/jps-builders/src/org/jetbrains/jps/server/ServerState.java @@ -313,7 +313,7 @@ class ServerState { lib.setClasspath(library.getPaths()); } else { - LOG.info("Failed to load global library " + lib.getName()); + LOG.info("Failed to load global library " + library.getName()); } } }