diff --git a/.idea/libraries/Guava.xml b/.idea/libraries/Guava.xml index fe0defcc233e..3024d55693eb 100644 --- a/.idea/libraries/Guava.xml +++ b/.idea/libraries/Guava.xml @@ -1,11 +1,11 @@ - + - + \ No newline at end of file diff --git a/build/scripts/libLicenses.gant b/build/scripts/libLicenses.gant index aada32a6e9e9..54de9a1663c2 100644 --- a/build/scripts/libLicenses.gant +++ b/build/scripts/libLicenses.gant @@ -150,7 +150,7 @@ libraryLicense(name: "Apache Lucene", libraryName: "lucene-core-2.4.1.jar", vers libraryLicense(name: "Automaton", libraryName: "automaton.jar", version: "1.11", license: "BSD", url: "http://www.brics.dk/automaton/", licenseUrl: "http://www.opensource.org/licenses/bsd-license.php") libraryLicense(name: "DTDParser", version: "1.13", license: "LGPL", url: "http://sourceforge.net/projects/dtdparser/", licenseUrl: "http://www.opensource.org/licenses/lgpl-2.1") libraryLicense(name: "Ganymed", version: "bundled with SVNKit", libraryName: "svnkit.jar", license: "BSD", url: "http://www.ganymed.ethz.ch/ssh2/", licenseUrl: "http://www.ganymed.ethz.ch/ssh2/LICENSE.txt") -libraryLicense(name: "Guava", version: "R6", license: "Apache 2.0", url: "http://code.google.com/p/guava-libraries/", licenseUrl: "http://ant.apache.org/license.html") +libraryLicense(name: "Guava", version: "R8", license: "Apache 2.0", url: "http://code.google.com/p/guava-libraries/", licenseUrl: "http://ant.apache.org/license.html") libraryLicense(name: "Gradle API", libraryName: "gradle-open-api-0.8.jar", version: "0.8", license: "Apache 2.0", url: "http://www.gradle.org/") libraryLicense(name: "Groovy", version: "1.7.3", license: "Apache 2.0", url: "http://groovy.codehaus.org/") libraryLicense(name: "Gson", libraryName: "gson-1.3.jar", license: "Apache 2.0", url: "http://code.google.com/p/google-gson/") @@ -170,6 +170,7 @@ libraryLicense(name: "JUnit", libraryName: "JUnit4", version: "4.8", license: "C libraryLicense(name: "Log4j", libraryName: "Log4J", version: "1.2", license: "Apache 2.0", url: "http://logging.apache.org/log4j/1.2/index.html", licenseUrl: "http://logging.apache.org/license.html") libraryLicense(name: "Maven", version: "2.2.1", license: "Apache 2.0", url: "http://maven.apache.org/", licenseUrl: "http://maven.apache.org/license.html") libraryLicense(name: "Maven3", libraryNames: ["Maven3", "maven-dependency-tree-1.2.jar"], version: "3.0.3", license: "Apache 2.0", url: "http://maven.apache.org/", licenseUrl: "http://maven.apache.org/license.html") +libraryLicense(name: "mercurial_prompthooks", attachedTo: "hg4idea", version: "", license: "GPLv2 (used as hg extension called from hg executable)", url: "https://github.com/willemv/mercurial_prompthooks", licenseUrl: "https://github.com/willemv/mercurial_prompthooks/blob/master/LICENSE.txt") libraryLicense(name: "Microba", libraryName: "microba", version: "0.4.2", license: "BSD", url: "http://microba.sourceforge.net/", licenseUrl: "http://microba.sourceforge.net/license.txt") libraryLicense(name: "NanoXML", version: "2.2.3", license: "zlib/libpng", url: "http://nanoxml.cyberelf.be/", licenseUrl: "http://devkix.com/nanoxml.php") libraryLicense(name: "Eclipse JDT Core", libraryName: "Eclipse", version: "3.3", license: "CPL 1.0", url: "http://www.eclipse.org/jdt/core/index.php") @@ -191,7 +192,7 @@ libraryLicense(name: "XML Commons (xml-apis.jar, resolver.jar)", version: "", li libraryLicense(name: "XMLBeans", libraryName: "XmlBeans", version: "2.3.0", license: "Apache 2.0", url: "http://xmlbeans.apache.org/", licenseUrl: "http://svn.jetbrains.org/idea/Trunk/bundled/WebServices/resources/lib/xmlbeans-2.3.0/xmlbeans.LICENSE") libraryLicense(name: "XML-RPC", libraryName: "XmlRPC", version: "2.0", license: "Apache 2.0", url: "http://ws.apache.org/xmlrpc/xmlrpc2/", licenseUrl: "http://ws.apache.org/xmlrpc/xmlrpc2/license.html") libraryLicense(name: "XStream", version: "1.2.1", license: "BSD", url: "http://xstream.codehaus.org/", licenseUrl: "http://xstream.codehaus.org/license.html") -libraryLicense(name: "YourKit Java Profiler", libraryName: "yjp-controller-api-redist.jar", version: "7.0.2", license: "link (commercial license)", url: "http://yourkit.com/", licenseUrl: "http://www.yourkit.com/purchase/license.html") +libraryLicense(name: "YourKit Java Profiler", libraryName: "yjp-controller-api-redist.jar", version: "8.0.x", license: "link (commercial license)", url: "http://yourkit.com/", licenseUrl: "http://www.yourkit.com/purchase/license.html") jetbrainsLibrary("JPS") jetbrainsLibrary("Maven Embedder") jetbrainsLibrary("tcServiceMessages") \ No newline at end of file diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/FacetBasedPackagingElement.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/FacetBasedPackagingElement.java new file mode 100644 index 000000000000..a34fdaf9a8fd --- /dev/null +++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/FacetBasedPackagingElement.java @@ -0,0 +1,29 @@ +/* + * Copyright 2000-2011 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 com.intellij.packaging.impl.elements; + +import com.intellij.facet.Facet; +import com.intellij.packaging.elements.PackagingElementResolvingContext; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author nik + */ +public interface FacetBasedPackagingElement { + @Nullable + Facet findFacet(@NotNull PackagingElementResolvingContext context); +} diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/ui/ArtifactElementPresentation.java b/java/compiler/impl/src/com/intellij/packaging/impl/ui/ArtifactElementPresentation.java index a9dabbf9f02d..a013fea361b5 100644 --- a/java/compiler/impl/src/com/intellij/packaging/impl/ui/ArtifactElementPresentation.java +++ b/java/compiler/impl/src/com/intellij/packaging/impl/ui/ArtifactElementPresentation.java @@ -47,11 +47,6 @@ public class ArtifactElementPresentation extends TreeNodePresentation { return findArtifact() != null; } - @Override - public Object getSourceObject() { - return myArtifactPointer; - } - @Override public void navigateToSource() { final Artifact artifact = findArtifact(); diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/ui/DelegatedPackagingElementPresentation.java b/java/compiler/impl/src/com/intellij/packaging/impl/ui/DelegatedPackagingElementPresentation.java index ccda22172fd6..8c5ac7d0fd93 100644 --- a/java/compiler/impl/src/com/intellij/packaging/impl/ui/DelegatedPackagingElementPresentation.java +++ b/java/compiler/impl/src/com/intellij/packaging/impl/ui/DelegatedPackagingElementPresentation.java @@ -57,11 +57,6 @@ public class DelegatedPackagingElementPresentation extends PackagingElementPrese myDelegate.navigateToSource(); } - @Nullable - public Object getSourceObject() { - return myDelegate.getSourceObject(); - } - public int getWeight() { return myDelegate.getWeight(); } diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/ui/LibraryElementPresentation.java b/java/compiler/impl/src/com/intellij/packaging/impl/ui/LibraryElementPresentation.java index b6a2a0117967..ab3fc860ee37 100644 --- a/java/compiler/impl/src/com/intellij/packaging/impl/ui/LibraryElementPresentation.java +++ b/java/compiler/impl/src/com/intellij/packaging/impl/ui/LibraryElementPresentation.java @@ -57,11 +57,6 @@ public class LibraryElementPresentation extends PackagingElementPresentation { return myLibrary != null; } - @Override - public Object getSourceObject() { - return myLibrary; - } - @Override public void navigateToSource() { myContext.selectLibrary(myLibrary); diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/ui/ModuleElementPresentation.java b/java/compiler/impl/src/com/intellij/packaging/impl/ui/ModuleElementPresentation.java index 47c793ba7b28..d01df08ad949 100644 --- a/java/compiler/impl/src/com/intellij/packaging/impl/ui/ModuleElementPresentation.java +++ b/java/compiler/impl/src/com/intellij/packaging/impl/ui/ModuleElementPresentation.java @@ -51,11 +51,6 @@ public class ModuleElementPresentation extends TreeNodePresentation { return findModule() != null; } - @Override - public Object getSourceObject() { - return findModule(); - } - @Nullable private Module findModule() { return myModulePointer != null ? myModulePointer.getModule() : null; diff --git a/java/compiler/openapi/src/com/intellij/packaging/ui/TreeNodePresentation.java b/java/compiler/openapi/src/com/intellij/packaging/ui/TreeNodePresentation.java index 6ef74756c8c1..96fe6ec5f7c2 100644 --- a/java/compiler/openapi/src/com/intellij/packaging/ui/TreeNodePresentation.java +++ b/java/compiler/openapi/src/com/intellij/packaging/ui/TreeNodePresentation.java @@ -45,6 +45,9 @@ public abstract class TreeNodePresentation { public void navigateToSource() { } + /** + * @deprecated this method is not used and will be removed in the future + */ @Nullable public Object getSourceObject() { return null; diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/ArtifactProjectStructureElement.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/ArtifactProjectStructureElement.java index 5d1dc1d32061..630f125a7b43 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/ArtifactProjectStructureElement.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/ArtifactProjectStructureElement.java @@ -15,6 +15,7 @@ */ package com.intellij.openapi.roots.ui.configuration.artifacts; +import com.intellij.facet.Facet; import com.intellij.openapi.module.Module; import com.intellij.openapi.roots.libraries.Library; import com.intellij.openapi.roots.ui.configuration.projectRoot.StructureConfigurableContext; @@ -25,9 +26,11 @@ import com.intellij.packaging.impl.artifacts.ArtifactUtil; import com.intellij.packaging.impl.artifacts.PackagingElementPath; import com.intellij.packaging.impl.artifacts.PackagingElementProcessor; import com.intellij.packaging.impl.elements.ArtifactPackagingElement; +import com.intellij.packaging.impl.elements.FacetBasedPackagingElement; import com.intellij.packaging.impl.elements.LibraryPackagingElement; import com.intellij.packaging.impl.elements.ModuleOutputPackagingElement; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; @@ -64,25 +67,10 @@ public class ArtifactProjectStructureElement extends ProjectStructureElement { ArtifactUtil.processPackagingElements(myArtifactsStructureContext.getRootElement(artifact), null, new PackagingElementProcessor>() { @Override public boolean process(@NotNull PackagingElement packagingElement, @NotNull PackagingElementPath path) { - if (packagingElement instanceof ModuleOutputPackagingElement) { - final Module module = ((ModuleOutputPackagingElement)packagingElement).findModule(myArtifactsStructureContext); - if (module != null) { - usages.add(createUsage(packagingElement, path, new ModuleProjectStructureElement(myContext, module))); - } - } - else if (packagingElement instanceof LibraryPackagingElement) { - final Library library = ((LibraryPackagingElement)packagingElement).findLibrary(myArtifactsStructureContext); - if (library != null) { - usages.add(createUsage(packagingElement, path, - new LibraryProjectStructureElement(ArtifactProjectStructureElement.this.myContext, library))); - } - } - else if (packagingElement instanceof ArtifactPackagingElement) { - final Artifact usedArtifact = ((ArtifactPackagingElement)packagingElement).findArtifact(myArtifactsStructureContext); - if (usedArtifact != null) { - final ArtifactProjectStructureElement artifactElement = myArtifactsStructureContext.getOrCreateArtifactElement(usedArtifact); - usages.add(createUsage(packagingElement, path, artifactElement)); - } + ProjectStructureElement element = getProjectStructureElementFor(packagingElement, ArtifactProjectStructureElement.this.myContext, + ArtifactProjectStructureElement.this.myArtifactsStructureContext); + if (element != null) { + usages.add(createUsage(packagingElement, path, element)); } return true; } @@ -90,6 +78,37 @@ public class ArtifactProjectStructureElement extends ProjectStructureElement { return usages; } + @Nullable + public static ProjectStructureElement getProjectStructureElementFor(PackagingElement packagingElement, + final StructureConfigurableContext context, + final ArtifactsStructureConfigurableContext artifactsStructureContext) { + if (packagingElement instanceof ModuleOutputPackagingElement) { + final Module module = ((ModuleOutputPackagingElement)packagingElement).findModule(artifactsStructureContext); + if (module != null) { + return new ModuleProjectStructureElement(context, module); + } + } + else if (packagingElement instanceof LibraryPackagingElement) { + final Library library = ((LibraryPackagingElement)packagingElement).findLibrary(artifactsStructureContext); + if (library != null) { + return new LibraryProjectStructureElement(context, library); + } + } + else if (packagingElement instanceof ArtifactPackagingElement) { + final Artifact usedArtifact = ((ArtifactPackagingElement)packagingElement).findArtifact(artifactsStructureContext); + if (usedArtifact != null) { + return artifactsStructureContext.getOrCreateArtifactElement(usedArtifact); + } + } + else if (packagingElement instanceof FacetBasedPackagingElement) { + Facet facet = ((FacetBasedPackagingElement)packagingElement).findFacet(artifactsStructureContext); + if (facet != null) { + return new FacetProjectStructureElement(context, facet); + } + } + return null; + } + private UsageInArtifact createUsage(PackagingElement packagingElement, PackagingElementPath path, final ProjectStructureElement element) { return new UsageInArtifact(myOriginalArtifact, myArtifactsStructureContext, element, this, path.getPathString(), packagingElement); diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/actions/ArtifactEditorFindUsagesActionBase.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/actions/ArtifactEditorFindUsagesActionBase.java index e635eeeb7a71..8ef65705503d 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/actions/ArtifactEditorFindUsagesActionBase.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/actions/ArtifactEditorFindUsagesActionBase.java @@ -15,21 +15,11 @@ */ package com.intellij.openapi.roots.ui.configuration.artifacts.actions; -import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; -import com.intellij.openapi.roots.libraries.Library; -import com.intellij.openapi.roots.ui.configuration.ProjectStructureConfigurable; import com.intellij.openapi.roots.ui.configuration.artifacts.ArtifactsStructureConfigurableContext; -import com.intellij.openapi.roots.ui.configuration.artifacts.nodes.ArtifactsTreeNode; import com.intellij.openapi.roots.ui.configuration.projectRoot.FindUsagesInProjectStructureActionBase; -import com.intellij.openapi.roots.ui.configuration.projectRoot.StructureConfigurableContext; -import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.LibraryProjectStructureElement; -import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ModuleProjectStructureElement; -import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ProjectStructureElement; -import com.intellij.packaging.artifacts.Artifact; import com.intellij.ui.awt.RelativePoint; import com.intellij.ui.treeStructure.Tree; -import org.jetbrains.annotations.Nullable; import java.awt.*; @@ -38,7 +28,7 @@ import java.awt.*; */ public abstract class ArtifactEditorFindUsagesActionBase extends FindUsagesInProjectStructureActionBase { private final Tree myTree; - private final ArtifactsStructureConfigurableContext myArtifactContext; + protected final ArtifactsStructureConfigurableContext myArtifactContext; public ArtifactEditorFindUsagesActionBase(Tree tree, Project project, ArtifactsStructureConfigurableContext artifactContext) { super(tree, project); @@ -47,33 +37,9 @@ public abstract class ArtifactEditorFindUsagesActionBase extends FindUsagesInPro } protected boolean isEnabled() { - ArtifactsTreeNode node = getSelectedNode(); - return node != null && node.getElementPresentation().getSourceObject() != null; + return getSelectedElement() != null; } - protected ProjectStructureElement getSelectedElement() { - ArtifactsTreeNode node = getSelectedNode(); - if (node == null) { - return null; - } - - final Object sourceObject = node.getElementPresentation().getSourceObject(); - final StructureConfigurableContext context = ProjectStructureConfigurable.getInstance(getContext().getProject()).getContext(); - if (sourceObject instanceof Module) { - return new ModuleProjectStructureElement(context, (Module)sourceObject); - } - else if (sourceObject instanceof Library) { - return new LibraryProjectStructureElement(context, (Library)sourceObject); - } - else if (sourceObject instanceof Artifact) { - return myArtifactContext.getOrCreateArtifactElement((Artifact)sourceObject); - } - return null; - } - - @Nullable - protected abstract ArtifactsTreeNode getSelectedNode(); - protected RelativePoint getPointToShowResults() { final int selectedRow = myTree.getSelectionRows()[0]; final Rectangle rowBounds = myTree.getRowBounds(selectedRow); diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/actions/LayoutTreeFindUsagesAction.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/actions/LayoutTreeFindUsagesAction.java index 74d4fb7eb392..e61b03630369 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/actions/LayoutTreeFindUsagesAction.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/actions/LayoutTreeFindUsagesAction.java @@ -16,10 +16,11 @@ package com.intellij.openapi.roots.ui.configuration.artifacts.actions; import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ui.configuration.artifacts.ArtifactProjectStructureElement; import com.intellij.openapi.roots.ui.configuration.artifacts.ArtifactsStructureConfigurableContext; import com.intellij.openapi.roots.ui.configuration.artifacts.LayoutTreeComponent; -import com.intellij.openapi.roots.ui.configuration.artifacts.nodes.ArtifactsTreeNode; -import org.jetbrains.annotations.Nullable; +import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ProjectStructureElement; +import com.intellij.packaging.elements.PackagingElement; /** * @author nik @@ -32,10 +33,10 @@ public class LayoutTreeFindUsagesAction extends ArtifactEditorFindUsagesActionBa myLayoutTreeComponent = layoutTreeComponent; } - @Override - @Nullable - protected ArtifactsTreeNode getSelectedNode() { - return myLayoutTreeComponent.getSelection().getNodeIfSingle(); - } + protected ProjectStructureElement getSelectedElement() { + PackagingElement packagingElement = myLayoutTreeComponent.getSelection().getElementIfSingle(); + if (packagingElement == null) return null; + return ArtifactProjectStructureElement.getProjectStructureElementFor(packagingElement, getContext(), myArtifactContext); + } } diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/DelegatedSourceItemPresentation.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/DelegatedSourceItemPresentation.java index a55d66998b90..ed2f78236a81 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/DelegatedSourceItemPresentation.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/DelegatedSourceItemPresentation.java @@ -57,11 +57,6 @@ public class DelegatedSourceItemPresentation extends SourceItemPresentation { myPresentation.navigateToSource(); } - @Nullable - public Object getSourceObject() { - return myPresentation.getSourceObject(); - } - public int getWeight() { return myPresentation.getWeight(); } diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/LibrarySourceItem.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/LibrarySourceItem.java index 52671c68b950..9f84cd6522bc 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/LibrarySourceItem.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/LibrarySourceItem.java @@ -90,11 +90,6 @@ public class LibrarySourceItem extends PackagingSourceItem { myContext.selectLibrary(myLibrary); } - @Override - public Object getSourceObject() { - return myLibrary; - } - @Override public String getPresentableName() { final String name = myLibrary.getName(); diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/ModuleOutputSourceItem.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/ModuleOutputSourceItem.java index 160f4c4e2599..9a45d0ecf4ce 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/ModuleOutputSourceItem.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/ModuleOutputSourceItem.java @@ -38,6 +38,10 @@ public class ModuleOutputSourceItem extends PackagingSourceItem { myModule = module; } + public Module getModule() { + return myModule; + } + public boolean equals(Object obj) { return obj instanceof ModuleOutputSourceItem && myModule.equals(((ModuleOutputSourceItem)obj).myModule); } diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/ModuleSourceItemGroup.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/ModuleSourceItemGroup.java index 302fdf74ce9a..45a5ae41ebcb 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/ModuleSourceItemGroup.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/ModuleSourceItemGroup.java @@ -140,11 +140,6 @@ public class ModuleSourceItemGroup extends PackagingSourceItem { myContext.selectModule(myModule); } - @Override - public Object getSourceObject() { - return myModule; - } - @Override public int getWeight() { return SourceItemWeights.MODULE_WEIGHT; diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/SourceItemNode.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/SourceItemNode.java index 4e9d6b88a507..35dcd13d1c2d 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/SourceItemNode.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/SourceItemNode.java @@ -47,7 +47,7 @@ public class SourceItemNode extends SourceItemNodeBase { } } - protected PackagingSourceItem getSourceItem() { + public PackagingSourceItem getSourceItem() { return mySourceItem; } } diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/actions/SourceItemFindUsagesAction.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/actions/SourceItemFindUsagesAction.java index b6a6bc66e296..332260de1ca3 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/actions/SourceItemFindUsagesAction.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/actions/SourceItemFindUsagesAction.java @@ -19,8 +19,12 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ui.configuration.artifacts.ArtifactsStructureConfigurableContext; import com.intellij.openapi.roots.ui.configuration.artifacts.actions.ArtifactEditorFindUsagesActionBase; import com.intellij.openapi.roots.ui.configuration.artifacts.nodes.ArtifactsTreeNode; -import com.intellij.openapi.roots.ui.configuration.artifacts.sourceItems.SourceItemNode; -import com.intellij.openapi.roots.ui.configuration.artifacts.sourceItems.SourceItemsTree; +import com.intellij.openapi.roots.ui.configuration.artifacts.sourceItems.*; +import com.intellij.openapi.roots.ui.configuration.projectRoot.StructureConfigurableContext; +import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.LibraryProjectStructureElement; +import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ModuleProjectStructureElement; +import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ProjectStructureElement; +import com.intellij.packaging.ui.PackagingSourceItem; import java.util.List; @@ -35,9 +39,27 @@ public class SourceItemFindUsagesAction extends ArtifactEditorFindUsagesActionBa myTree = tree; } - @Override - protected ArtifactsTreeNode getSelectedNode() { + protected ProjectStructureElement getSelectedElement() { final List nodes = myTree.getSelectedSourceItemNodes(); - return nodes.size() == 1 ? nodes.get(0) : null; + if (nodes.size() != 1) return null; + ArtifactsTreeNode node = nodes.get(0); + if (!(node instanceof SourceItemNode)) { + return null; + } + + PackagingSourceItem sourceItem = ((SourceItemNode)node).getSourceItem(); + if (sourceItem == null) return null; + + final StructureConfigurableContext context = getContext(); + if (sourceItem instanceof ModuleOutputSourceItem) { + return new ModuleProjectStructureElement(context, ((ModuleOutputSourceItem)sourceItem).getModule()); + } + else if (sourceItem instanceof LibrarySourceItem) { + return new LibraryProjectStructureElement(context, ((LibrarySourceItem)sourceItem).getLibrary()); + } + else if (sourceItem instanceof ArtifactSourceItem) { + return myArtifactContext.getOrCreateArtifactElement(((ArtifactSourceItem)sourceItem).getArtifact()); + } + return null; } } diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/FacetConfigurable.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/FacetConfigurable.java index 4a3ed0c67da0..da6fd0bb6cc0 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/FacetConfigurable.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/FacetConfigurable.java @@ -23,7 +23,8 @@ import com.intellij.facet.impl.ui.FacetEditorImpl; import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.project.ProjectBundle; import com.intellij.openapi.roots.ui.configuration.ModulesConfigurator; -import com.intellij.openapi.ui.NamedConfigurable; +import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.FacetProjectStructureElement; +import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ProjectStructureElement; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.Nullable; @@ -33,16 +34,18 @@ import javax.swing.*; /** * @author nik */ -public class FacetConfigurable extends NamedConfigurable { +public class FacetConfigurable extends ProjectStructureElementConfigurable { private final Facet myFacet; private final ModulesConfigurator myModulesConfigurator; private String myFacetName; + private FacetProjectStructureElement myProjectStructureElement; - public FacetConfigurable(final Facet facet, final ModulesConfigurator modulesConfigurator, final Runnable updateTree) { + public FacetConfigurable(final Facet facet, final StructureConfigurableContext context, final Runnable updateTree) { super(!facet.getType().isOnlyOneFacetAllowed() && !(facet instanceof InvalidFacet), updateTree); myFacet = facet; - myModulesConfigurator = modulesConfigurator; + myModulesConfigurator = context.getModulesConfigurator(); myFacetName = myFacet.getName(); + myProjectStructureElement = new FacetProjectStructureElement(context, facet); } @@ -54,6 +57,11 @@ public class FacetConfigurable extends NamedConfigurable { } } + @Override + public ProjectStructureElement getProjectStructureElement() { + return myProjectStructureElement; + } + private ProjectFacetsConfigurator getFacetsConfigurator() { return myModulesConfigurator.getFacetsConfigurator(); } diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/FacetEditorFacadeImpl.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/FacetEditorFacadeImpl.java index 59b641ded9e8..92670f6b0fe8 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/FacetEditorFacadeImpl.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/FacetEditorFacadeImpl.java @@ -87,7 +87,7 @@ public class FacetEditorFacadeImpl implements FacetEditorFacade { public FacetConfigurable getOrCreateConfigurable(final Facet facet) { FacetConfigurable configurable = myConfigurables.get(facet); if (configurable == null) { - configurable = new FacetConfigurable(facet, myStructureConfigurable.getContext().getModulesConfigurator(), myTreeUpdater); + configurable = new FacetConfigurable(facet, myStructureConfigurable.getContext(), myTreeUpdater); myConfigurables.put(facet, configurable); } return configurable; diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/FacetStructureConfigurable.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/FacetStructureConfigurable.java index b94f19f0b99d..b56c5a1fc4be 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/FacetStructureConfigurable.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/FacetStructureConfigurable.java @@ -36,6 +36,8 @@ import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectBundle; import com.intellij.openapi.roots.ui.configuration.ProjectStructureConfigurable; +import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.FacetProjectStructureElement; +import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ProjectStructureElement; import com.intellij.openapi.ui.DetailsComponent; import com.intellij.openapi.ui.NamedConfigurable; import com.intellij.openapi.util.IconLoader; @@ -99,6 +101,19 @@ public class FacetStructureConfigurable extends BaseStructureConfigurable { } } + @NotNull + @Override + protected Collection getProjectStructureElements() { + List elements = new ArrayList(); + for (Module module : myModuleManager.getModules()) { + Facet[] facets = FacetManager.getInstance(module).getAllFacets(); + for (Facet facet : facets) { + elements.add(new FacetProjectStructureElement(myContext, facet)); + } + } + return elements; + } + public MyNode findFacetTypeNode(FacetType facetType) { return findNodeByObject(myRoot, facetType); } @@ -106,6 +121,7 @@ public class FacetStructureConfigurable extends BaseStructureConfigurable { public void addFacetNode(@NotNull MyNode facetTypeNode, @NotNull Facet facet, @NotNull FacetEditorFacadeImpl editorFacade) { FacetConfigurable facetConfigurable = editorFacade.getOrCreateConfigurable(facet); addNode(new FacetConfigurableNode(facetConfigurable), facetTypeNode); + myContext.getDaemonAnalyzer().queueUpdate(new FacetProjectStructureElement(myContext, facet)); } @Nullable @@ -177,6 +193,9 @@ public class FacetStructureConfigurable extends BaseStructureConfigurable { protected List removeFacet(final Facet facet) { List removed = super.removeFacet(facet); ModuleStructureConfigurable.getInstance(myProject).removeFacetNodes(removed); + for (Facet removedFacet : removed) { + myContext.getDaemonAnalyzer().removeElement(new FacetProjectStructureElement(myContext, removedFacet)); + } return removed; } diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/FacetProjectStructureElement.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/FacetProjectStructureElement.java new file mode 100644 index 000000000000..c2a1ce1e59f0 --- /dev/null +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/daemon/FacetProjectStructureElement.java @@ -0,0 +1,64 @@ +/* + * Copyright 2000-2011 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 com.intellij.openapi.roots.ui.configuration.projectRoot.daemon; + +import com.intellij.facet.Facet; +import com.intellij.openapi.roots.ui.configuration.projectRoot.StructureConfigurableContext; +import org.jetbrains.annotations.NotNull; + +import java.util.Collections; +import java.util.List; + +/** + * @author nik + */ +public class FacetProjectStructureElement extends ProjectStructureElement { + private final Facet myFacet; + + public FacetProjectStructureElement(@NotNull StructureConfigurableContext context, @NotNull Facet facet) { + super(context); + myFacet = facet; + } + + @Override + public void check(ProjectStructureProblemsHolder problemsHolder) { + } + + @Override + public List getUsagesInElement() { + return Collections.emptyList(); + } + + @Override + public boolean highlightIfUnused() { + return false; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof FacetProjectStructureElement && myFacet.equals(((FacetProjectStructureElement)obj).myFacet); + } + + @Override + public int hashCode() { + return myFacet.hashCode(); + } + + @Override + public String toString() { + return "facet:" + myFacet.getName() + " in " + myFacet.getModule().getName(); + } +} diff --git a/java/java-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspection.java b/java/java-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspection.java index e59f562af8b8..ad128d05b993 100644 --- a/java/java-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspection.java +++ b/java/java-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspection.java @@ -74,7 +74,7 @@ public class NullableStuffInspection extends BaseLocalInspectionTool { final PsiParameterList parameterList = psiMethod.getParameterList(); final PsiParameter[] parameters = parameterList.getParameters(); final PsiExpression[] expressions = expression.getArgumentList().getExpressions(); - for (int i = 0, expressionsLength = expressions.length; i < Math.max(expressionsLength, parameters.length); i++) { + for (int i = 0, expressionsLength = expressions.length; i < Math.min(expressionsLength, parameters.length); i++) { PsiExpression psiExpression = expressions[i]; if (psiExpression.getType() == PsiType.NULL) { if (!AnnotationUtil.isNullable(parameters[i]) && !AnnotationUtil.isNotNull(parameters[i])) { diff --git a/java/java-impl/src/com/intellij/psi/impl/smartPointers/AnchorElementInfo.java b/java/java-impl/src/com/intellij/psi/impl/smartPointers/AnchorElementInfo.java index de5f30b56da0..02612fae3d7a 100644 --- a/java/java-impl/src/com/intellij/psi/impl/smartPointers/AnchorElementInfo.java +++ b/java/java-impl/src/com/intellij/psi/impl/smartPointers/AnchorElementInfo.java @@ -52,12 +52,12 @@ class AnchorElementInfo extends SelfElementInfo { @Nullable public PsiElement restoreElement() { if (stubId != -1) { - PsiFile file = SelfElementInfo.restoreFileFromVirtual(myVirtualFile, myProject); + PsiFile file = SelfElementInfo.restoreFileFromVirtual(getVirtualFile(), myProject); if (!(file instanceof PsiFileWithStubSupport)) return null; return PsiAnchor.restoreFromStubIndex((PsiFileWithStubSupport)file, stubId, myStubElementType); } if (!mySyncMarkerIsValid) return null; - PsiFile file = SelfElementInfo.restoreFileFromVirtual(myVirtualFile, myProject); + PsiFile file = SelfElementInfo.restoreFileFromVirtual(getVirtualFile(), myProject); if (file == null) return null; PsiElement anchor = file.findElementAt(getSyncStartOffset()); if (anchor == null) return null; diff --git a/java/java-impl/src/com/intellij/psi/impl/smartPointers/ImplicitVariableElementInfoFactory.java b/java/java-impl/src/com/intellij/psi/impl/smartPointers/ImplicitVariableElementInfoFactory.java index 37ee533d4a27..53888a5a2b69 100644 --- a/java/java-impl/src/com/intellij/psi/impl/smartPointers/ImplicitVariableElementInfoFactory.java +++ b/java/java-impl/src/com/intellij/psi/impl/smartPointers/ImplicitVariableElementInfoFactory.java @@ -77,7 +77,7 @@ public class ImplicitVariableElementInfoFactory implements SmartPointerElementIn } @Override - public boolean pointsToTheSameElementAs(SmartPointerElementInfo other) { + public boolean pointsToTheSameElementAs(@NotNull SmartPointerElementInfo other) { if (other instanceof ImplicitVariableInfo) { return myVar == ((ImplicitVariableInfo)other).myVar; } diff --git a/java/java-impl/src/com/intellij/refactoring/introduceField/InplaceIntroduceConstantPopup.java b/java/java-impl/src/com/intellij/refactoring/introduceField/InplaceIntroduceConstantPopup.java index 5f34e07a9ab3..1d0f6672106b 100644 --- a/java/java-impl/src/com/intellij/refactoring/introduceField/InplaceIntroduceConstantPopup.java +++ b/java/java-impl/src/com/intellij/refactoring/introduceField/InplaceIntroduceConstantPopup.java @@ -19,7 +19,13 @@ import com.intellij.codeInsight.AnnotationUtil; import com.intellij.codeInsight.intention.impl.TypeExpression; import com.intellij.codeInsight.template.impl.TemplateManagerImpl; import com.intellij.codeInsight.template.impl.TemplateState; +import com.intellij.ide.ui.ListCellRendererWrapper; +import com.intellij.ide.ui.UISettings; import com.intellij.ide.util.PropertiesComponent; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CustomShortcutSet; +import com.intellij.openapi.actionSystem.KeyboardShortcut; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.editor.Editor; @@ -28,6 +34,7 @@ import com.intellij.openapi.editor.ScrollType; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.LanguageLevelProjectExtension; import com.intellij.openapi.util.Computable; +import com.intellij.openapi.wm.ToolWindowManager; import com.intellij.psi.*; import com.intellij.psi.codeStyle.JavaCodeStyleManager; import com.intellij.psi.codeStyle.VariableKind; @@ -42,11 +49,11 @@ import com.intellij.refactoring.ui.TypeSelectorManagerImpl; import com.intellij.refactoring.util.occurences.OccurenceManager; import com.intellij.ui.StateRestoringCheckBox; import com.intellij.ui.TitlePanel; +import com.intellij.util.VisibilityUtil; import javax.swing.*; import java.awt.*; -import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; +import java.awt.event.*; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashSet; @@ -62,7 +69,7 @@ public class InplaceIntroduceConstantPopup { private PsiExpression myExpr; private final PsiLocalVariable myLocalVariable; private final PsiExpression[] myOccurrences; - private final TypeSelectorManagerImpl myTypeSelectorManager; + private TypeSelectorManagerImpl myTypeSelectorManager; private PsiElement myAnchorElement; private int myAnchorIdx = -1; private PsiElement myAnchorElementIfAll; @@ -83,7 +90,7 @@ public class InplaceIntroduceConstantPopup { private JCheckBox myMoveToAnotherClassCb; - private JavaVisibilityPanel myVisibilityPanel; + private JComboBox myVisibilityCombo; private JPanel myWholePanel; @@ -122,31 +129,36 @@ public class InplaceIntroduceConstantPopup { myLocalName = localVariable != null ? localVariable.getName() : null; myWholePanel = new JPanel(new GridBagLayout()); - GridBagConstraints gc = new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 1, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0 , 0, 0), 0, 0); + GridBagConstraints gc = new GridBagConstraints(0, 0, 1, 1, 1, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0 , 0, 0), 0, 0); final TitlePanel titlePanel = new TitlePanel(); titlePanel.setBorder(null); titlePanel.setText(IntroduceConstantHandler.REFACTORING_NAME); - + gc.gridwidth = 2; myWholePanel.add(titlePanel, gc); - gc.insets = new Insets(5, 5, 5, 5); + gc.gridwidth = 1; + gc.gridy = 1; + myWholePanel.add(createLeftPanel(), gc); - myVisibilityPanel = new JavaVisibilityPanel(false, false); - myVisibilityPanel.setVisibility(JavaRefactoringSettings.getInstance().INTRODUCE_CONSTANT_VISIBILITY); - myWholePanel.add(myVisibilityPanel, gc); + gc.gridx = 1; + gc.insets.left = 6; + myWholePanel.add(createRightPanel(), gc); + } + + private JPanel createRightPanel() { + final JPanel right = new JPanel(new GridBagLayout()); + final GridBagConstraints rgc = new GridBagConstraints(0, GridBagConstraints.RELATIVE,1,1,0,0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(1,0,0,0), 0, 0); myReplaceAllCb = new JCheckBox("Replace all occurrences"); myReplaceAllCb.setMnemonic('a'); myReplaceAllCb.setFocusable(false); - myWholePanel.add(myReplaceAllCb, gc); myReplaceAllCb.setVisible(myOccurrences.length > 1); - + right.add(myReplaceAllCb, rgc); myCbDeleteVariable = new StateRestoringCheckBox("Delete variable declaration"); myCbDeleteVariable.setMnemonic('d'); myCbDeleteVariable.setFocusable(false); - myWholePanel.add(myCbDeleteVariable, gc); if (myLocalVariable != null) { if (myReplaceAllCb != null) { myReplaceAllCb.setEnabled(false); @@ -157,15 +169,15 @@ public class InplaceIntroduceConstantPopup { } else { myCbDeleteVariable.setVisible(false); } + right.add(myCbDeleteVariable, rgc); myAnnotateNonNls = new JCheckBox("Annotate field as @NonNls"); myAnnotateNonNls.setMnemonic('f'); myAnnotateNonNls.setFocusable(false); - myWholePanel.add(myAnnotateNonNls, gc); - final PsiManager psiManager = PsiManager.getInstance(myProject); - if ((myTypeSelectorManager.isSuggestedType("java.lang.String") || (myLocalVariable != null && AnnotationUtil.isAnnotated(myLocalVariable, AnnotationUtil.NON_NLS, false)))&& - LanguageLevelProjectExtension.getInstance(psiManager.getProject()).getLanguageLevel().hasEnumKeywordAndAutoboxing() && - JavaPsiFacade.getInstance(psiManager.getProject()).findClass(AnnotationUtil.NON_NLS, myParentClass.getResolveScope()) != null) { + if ((myTypeSelectorManager.isSuggestedType("java.lang.String") || (myLocalVariable != null && AnnotationUtil + .isAnnotated(myLocalVariable, AnnotationUtil.NON_NLS, false)))&& + LanguageLevelProjectExtension.getInstance(myProject).getLanguageLevel().hasEnumKeywordAndAutoboxing() && + JavaPsiFacade.getInstance(myProject).findClass(AnnotationUtil.NON_NLS, myParentClass.getResolveScope()) != null) { final PropertiesComponent component = PropertiesComponent.getInstance(myProject); myAnnotateNonNls.setSelected(component.isTrueValue(IntroduceConstantDialog.NONNLS_SELECTED_PROPERTY)); myAnnotateNonNls.addItemListener(new ItemListener() { @@ -176,13 +188,69 @@ public class InplaceIntroduceConstantPopup { } else { myAnnotateNonNls.setVisible(false); } + right.add(myAnnotateNonNls, rgc); + return right; + } + + private JPanel createLeftPanel() { + final JPanel left = new JPanel(new GridBagLayout()); + final GridBagConstraints lgc = new GridBagConstraints(0, 0, 1, 1, 0, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(5,5,0,0), 0, 0); + + final JLabel label = new JLabel("Visibility:"); + label.setDisplayedMnemonic('V'); + left.add(label, lgc); + myVisibilityCombo = new JComboBox(new String[]{PsiModifier.PUBLIC, PsiModifier.PACKAGE_LOCAL, PsiModifier.PROTECTED, PsiModifier.PRIVATE}); + myVisibilityCombo.setRenderer(new ListCellRendererWrapper(myVisibilityCombo.getRenderer()){ + @Override + public void customize(JList list, String value, int index, boolean selected, boolean hasFocus) { + setText(PsiBundle.visibilityPresentation(value)); + } + }); + label.setLabelFor(myVisibilityCombo); + myVisibilityCombo.setSelectedItem(JavaRefactoringSettings.getInstance().INTRODUCE_CONSTANT_VISIBILITY); + myVisibilityCombo.addKeyListener(new KeyAdapter() { + @Override + public void keyReleased(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { + ToolWindowManager.getInstance(myProject).activateEditorComponent(); + } + } + }); + final AnAction arrow = new AnAction() { + @Override + public void actionPerformed(AnActionEvent e) { + if (e.getInputEvent() instanceof KeyEvent) { + final int code = ((KeyEvent)e.getInputEvent()).getKeyCode(); + final int delta = code == KeyEvent.VK_DOWN ? 1 : code == KeyEvent.VK_UP ? -1 : 0; + if (delta == 0) return; + final int size = myVisibilityCombo.getModel().getSize(); + int next = myVisibilityCombo.getSelectedIndex() + delta; + if (next < 0 || next >= size) { + if (!UISettings.getInstance().CYCLE_SCROLLING) { + return; + } + next = (next + size) % size; + } + myVisibilityCombo.setSelectedIndex(next); + } + } + }; + arrow.registerCustomShortcutSet(new CustomShortcutSet(new KeyboardShortcut(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), null), + new KeyboardShortcut(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), null)), myVisibilityCombo); + lgc.gridx = 1; + lgc.insets.top = 2; + lgc.insets.left = 0; + left.add(myVisibilityCombo, lgc); myMoveToAnotherClassCb = new JCheckBox("Move to another class"); myMoveToAnotherClassCb.setMnemonic('m'); myMoveToAnotherClassCb.setFocusable(false); - myWholePanel.add(myMoveToAnotherClassCb, gc); - - + lgc.gridx = 0; + lgc.gridy = 1; + lgc.gridwidth = 2; + lgc.insets.top = 0; + left.add(myMoveToAnotherClassCb, lgc); + return left; } public void performInplaceIntroduce() { @@ -220,7 +288,7 @@ public class InplaceIntroduceConstantPopup { PsiField field = elementFactory.createFieldFromText(psiType.getCanonicalText() + " " + (myConstantName != null ? myConstantName : names[0]) + " = " + myExprText + ";", myParentClass); PsiUtil.setModifierProperty(field, PsiModifier.FINAL, true); PsiUtil.setModifierProperty(field, PsiModifier.STATIC, true); - final String visibility = myVisibilityPanel.getVisibility(); + final String visibility = getSelectedVisibility(); if (visibility != null) { PsiUtil.setModifierProperty(field, visibility, true); } @@ -230,6 +298,10 @@ public class InplaceIntroduceConstantPopup { }); } + private String getSelectedVisibility() { + return (String)myVisibilityCombo.getSelectedItem(); + } + public List getOccurrenceMarkers() { if (myOccurrenceMarkers == null) { myOccurrenceMarkers = new ArrayList(); @@ -301,7 +373,7 @@ public class InplaceIntroduceConstantPopup { @Override protected void saveSettings(PsiVariable psiVariable) { TypeSelectorManagerImpl.typeSelected(psiVariable.getType(), myDefaultParameterTypePointer.getType()); - JavaRefactoringSettings.getInstance().INTRODUCE_CONSTANT_VISIBILITY = myVisibilityPanel.getVisibility(); + JavaRefactoringSettings.getInstance().INTRODUCE_CONSTANT_VISIBILITY = getSelectedVisibility(); } @Override @@ -321,7 +393,7 @@ public class InplaceIntroduceConstantPopup { isReplaceAllOccurrences(), true, true, BaseExpressionToFieldHandler.InitializationPlace.IN_FIELD_DECLARATION, - myVisibilityPanel.getVisibility(), myLocalVariable, + getSelectedVisibility(), myLocalVariable, myFieldTypePointer.getType(), isDeleteVariable(), myParentClass, isAnnotateNonNls(), false); @@ -358,10 +430,16 @@ public class InplaceIntroduceConstantPopup { protected JComponent getComponent() { if (!myInitListeners) { myInitListeners = true; - myVisibilityPanel.addListener(new VisibilityListener(myProject, myEditor){ + final VisibilityListener visibilityListener = new VisibilityListener(myProject, myEditor) { @Override protected String getVisibility() { - return myVisibilityPanel.getVisibility(); + return getSelectedVisibility(); + } + }; + myVisibilityCombo.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + visibilityListener.stateChanged(null); } }); myReplaceAllCb.addItemListener(new ItemListener() { @@ -370,6 +448,7 @@ public class InplaceIntroduceConstantPopup { final TemplateState templateState = TemplateManagerImpl.getTemplateState(myEditor); if (templateState != null) { templateState.gotoEnd(true); + myTypeSelectorManager = new TypeSelectorManagerImpl(myProject, myFieldTypePointer.getType(), null, myExpr, myOccurrences); startIntroduceTemplate(isReplaceAllOccurrences()); } } diff --git a/java/java-impl/src/com/intellij/refactoring/introduceField/InplaceIntroduceFieldPopup.java b/java/java-impl/src/com/intellij/refactoring/introduceField/InplaceIntroduceFieldPopup.java index 6a8f6e64a3eb..ba9e22049290 100644 --- a/java/java-impl/src/com/intellij/refactoring/introduceField/InplaceIntroduceFieldPopup.java +++ b/java/java-impl/src/com/intellij/refactoring/introduceField/InplaceIntroduceFieldPopup.java @@ -59,7 +59,7 @@ public class InplaceIntroduceFieldPopup { private final Editor myEditor; private Project myProject; - private final TypeSelectorManagerImpl myTypeSelectorManager; + private TypeSelectorManagerImpl myTypeSelectorManager; private final PsiElement myAnchorElement; private int myAnchorIdx = -1; private final PsiElement myAnchorElementIfAll; @@ -136,16 +136,7 @@ public class InplaceIntroduceFieldPopup { myWholePanel.add(myIntroduceFieldPanel.createCenterPanel(), gc); myIntroduceFieldPanel.initializeControls(initializerExpression, ourLastInitializerPlace); - myIntroduceFieldPanel.addOccurrenceListener(new ItemListener() { - @Override - public void itemStateChanged(ItemEvent e) { - final TemplateState templateState = TemplateManagerImpl.getTemplateState(myEditor); - if (templateState != null) { - templateState.gotoEnd(true); - startTemplate(myIntroduceFieldPanel.isReplaceAllOccurrences()); - } - } - }); + } @@ -298,6 +289,17 @@ public class InplaceIntroduceFieldPopup { finalListener.perform(myIntroduceFieldPanel.isDeclareFinal()); } }); + myIntroduceFieldPanel.addOccurrenceListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + final TemplateState templateState = TemplateManagerImpl.getTemplateState(myEditor); + if (templateState != null) { + templateState.gotoEnd(true); + myTypeSelectorManager = new TypeSelectorManagerImpl(myProject, myFieldTypePointer.getType(), null, myInitializerExpression, myOccurrences); + startTemplate(myIntroduceFieldPanel.isReplaceAllOccurrences()); + } + } + }); } return myWholePanel; diff --git a/lib/guava-r08.jar b/lib/guava-r08.jar deleted file mode 100644 index d18d0bdae1dc..000000000000 Binary files a/lib/guava-r08.jar and /dev/null differ diff --git a/lib/guava-r09.jar b/lib/guava-r09.jar new file mode 100644 index 000000000000..f8da8b1c1e81 Binary files /dev/null and b/lib/guava-r09.jar differ diff --git a/lib/required_for_dist.txt b/lib/required_for_dist.txt index 1d417dd64189..d16543be9c74 100644 --- a/lib/required_for_dist.txt +++ b/lib/required_for_dist.txt @@ -10,7 +10,7 @@ commons-logging-1.1.1.jar commons-net-2.0-patched.jar dtdparser113.jar groovy-all-1.7.3.jar -guava-r08.jar +guava-r09.jar jaxen-1.1.3.jar jcip-annotations.jar jdom.jar diff --git a/lib/src/guava-src-r08.zip b/lib/src/guava-src-r08.zip deleted file mode 100644 index 3ca29c2d28e6..000000000000 Binary files a/lib/src/guava-src-r08.zip and /dev/null differ diff --git a/lib/src/guava-src-r09.zip b/lib/src/guava-src-r09.zip new file mode 100644 index 000000000000..2bb76991e904 Binary files /dev/null and b/lib/src/guava-src-r09.zip differ diff --git a/platform/lang-api/src/com/intellij/psi/SmartPsiFileRange.java b/platform/lang-api/src/com/intellij/psi/SmartPsiFileRange.java index 2b2c59bed935..01d89f8af3d5 100644 --- a/platform/lang-api/src/com/intellij/psi/SmartPsiFileRange.java +++ b/platform/lang-api/src/com/intellij/psi/SmartPsiFileRange.java @@ -16,6 +16,7 @@ package com.intellij.psi; import com.intellij.openapi.util.Segment; +import org.jetbrains.annotations.Nullable; /** * pointer to a PsiFile + range inside the file. @@ -25,5 +26,6 @@ public interface SmartPsiFileRange extends SmartPsiElementPointer { /** * @return the range inside the PsiFile, or null if the range or PsiFile became invalid */ + @Nullable Segment getRange(); } diff --git a/platform/lang-api/src/com/intellij/psi/impl/ElementBase.java b/platform/lang-api/src/com/intellij/psi/impl/ElementBase.java index 3aabf8906f7d..0da77f560d2c 100644 --- a/platform/lang-api/src/com/intellij/psi/impl/ElementBase.java +++ b/platform/lang-api/src/com/intellij/psi/impl/ElementBase.java @@ -97,11 +97,11 @@ public abstract class ElementBase extends UserDataHolderBase implements Iconable baseIcon = myBaseIcon.get(flags); } + if (!psiElement.isValid()) return null; if (isToDeferIconLoading()) { return IconDeferrer.getInstance().defer(baseIcon, new ElementIconRequest(psiElement, flags), ICON_COMPUTE); } else { - if (!psiElement.isValid()) return null; return computeIconNow(psiElement, flags); } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/IndentsPass.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/IndentsPass.java index e8042f3c980d..063fcc108055 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/IndentsPass.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/IndentsPass.java @@ -160,7 +160,7 @@ public class IndentsPass extends TextEditorHighlightingPass implements DumbAware int newY = start.y; SoftWrapModel softWrapModel = editor.getSoftWrapModel(); int lineHeight = editor.getLineHeight(); - for (int i = startLine + lineShift; i < endLine && newY < maxY; i++) { + for (int i = Math.max(0, startLine + lineShift); i < endLine && newY < maxY; i++) { List softWraps = softWrapModel.getSoftWrapsForLine(i); int logicalLineHeight = softWraps.size() * lineHeight; if (i > startLine + lineShift) { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/JoinLinesHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/JoinLinesHandler.java index 0ff56c825a0e..2ea11eb84164 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/JoinLinesHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/JoinLinesHandler.java @@ -28,6 +28,7 @@ import com.intellij.ide.DataManager; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.actionSystem.PlatformDataKeys; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.LogicalPosition; import com.intellij.openapi.editor.ScrollType; @@ -42,8 +43,10 @@ import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.codeStyle.CodeStyleManager; +import com.intellij.psi.impl.source.codeStyle.CodeEditUtil; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.IncorrectOperationException; +import org.jetbrains.annotations.NotNull; import static com.intellij.codeInsight.editorActions.JoinLinesHandlerDelegate.CANNOT_JOIN; @@ -160,7 +163,7 @@ public class JoinLinesHandler extends EditorWriteActionHandler { if (rc != CANNOT_JOIN) break; } } - docManager.doPostponedOperationsAndUnblockDocument(doc); + doPostponedOperationsAndUnblockDocument(docManager, doc); if (rc != CANNOT_JOIN) { if (caretRestoreOffset == CANNOT_JOIN) caretRestoreOffset = rc; @@ -209,7 +212,7 @@ public class JoinLinesHandler extends EditorWriteActionHandler { } if (prevLineCount < doc.getLineCount()) { - docManager.doPostponedOperationsAndUnblockDocument(doc); + doPostponedOperationsAndUnblockDocument(docManager, doc); end = doc.getLineEndOffset(startLine) + doc.getLineSeparatorLength(startLine); start = end - doc.getLineSeparatorLength(startLine); int addedLinesCount = doc.getLineCount() - prevLineCount - 1; @@ -235,6 +238,19 @@ public class JoinLinesHandler extends EditorWriteActionHandler { } } + private static void doPostponedOperationsAndUnblockDocument(@NotNull PsiDocumentManager docManager, @NotNull DocumentEx document) { + boolean restore = CodeEditUtil.isSuspendedNodesReformattingAllowed(); + CodeEditUtil.setAllowSuspendNodesReformatting(false); + try { + docManager.doPostponedOperationsAndUnblockDocument(document); + } + finally { + if (restore) { + CodeEditUtil.setAllowSuspendNodesReformatting(true); + } + } + } + private static boolean isCommentElement(final PsiElement element) { return element != null && PsiTreeUtil.getParentOfType(element, PsiComment.class, false) != null; } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/FoldingUtil.java b/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/FoldingUtil.java index 9fdf1aaf7eb8..8fbfea96ee1d 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/FoldingUtil.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/FoldingUtil.java @@ -45,8 +45,11 @@ class FoldingUtil { } public static FoldRegion findFoldRegionStartingAtLine(Editor editor, int line){ - FoldRegion[] regions = editor.getFoldingModel().getAllFoldRegions(); FoldRegion result = null; + if (line >= editor.getDocument().getLineCount()) { + return result; + } + FoldRegion[] regions = editor.getFoldingModel().getAllFoldRegions(); for (FoldRegion region : regions) { if (region.getDocument().getLineNumber(region.getStartOffset()) == line) { if (result != null) return null; diff --git a/platform/lang-impl/src/com/intellij/execution/impl/ConsoleBuffer.java b/platform/lang-impl/src/com/intellij/execution/impl/ConsoleBuffer.java new file mode 100644 index 000000000000..8d0b691364a1 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/execution/impl/ConsoleBuffer.java @@ -0,0 +1,604 @@ +/* + * Copyright 2000-2011 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 com.intellij.execution.impl; + +import com.intellij.execution.filters.HyperlinkInfo; +import com.intellij.execution.ui.ConsoleViewContentType; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.util.text.StringUtil; +import gnu.trove.TIntArrayList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +import static com.intellij.execution.impl.ConsoleViewImpl.HyperlinkTokenInfo; +import static com.intellij.execution.impl.ConsoleViewImpl.TokenInfo; + +/** + * IJ user may want the console to use cyclic buffer, i.e. don't keep more than particular amount of symbols. So, we need + * to have a data structure that allow to achieve that. This class serves for that purpose. + *

+ * Not thread-safe. + *

+ * Note: basically this class consists of functionality that is cut from {@link ConsoleViewImpl} in order to make it possible + * to cover it by tests. + * + * @author Denis Zhdanov + * @since 4/5/11 5:26 PM + */ +public class ConsoleBuffer { + + private static final int DEFAULT_CYCLIC_BUFFER_UNIT_SIZE = 256; + + private static final boolean DEBUG_PROCESSING = false; + + /** + * Buffer for deferred stdout, stderr and stdin output. + *

+ * Feel free to check rationale for using this approach at {@link #myCyclicBufferSize} contract. + */ + private final Deque myDeferredOutput = new ArrayDeque(); + private final Set myContentTypesToNotStripOnCycling = new HashSet(); + + /** + * Main console usage scenario assumes the following: + *

+   * 
    + *
  • + * console may be {@link ConsoleViewImpl#print(String, ConsoleViewContentType) provided} with the new text from any thread + * (e.g. separate thread is charged for reading output of java application launched under IJ. That output is provided + * to the console); + *
  • + *
  • current class flushes provided text to {@link Editor editor} used for representing it to end-user from EDT;
  • + *
  • + * dedicated buffer is kept to hold console text between the moment when it's provided to the current class + * and flush to the editor;
  • + *
+ *
+ *

+ * It's also possible to configure console to use cyclic buffer in order to avoid unnecessary memory consumption. + * However, that implies possibility of the following situation - console user provides it with the great number + * of small chunks of text (that is the case for junit processing). It's inappropriate to use single {@link StringBuilder} as + * a buffer then because every time we see that cyclic buffer size is exceeded and we need to cut exceeding text from buffer + * start, trailing part is moved to the zero offset. That produces extensive CPU usage in case of great number of small messages + * where every such message exceeds cyclic buffer size. + *

+ * That is the reason why we use data structure similar to STL deque here - we hold number of string buffers of small size instead + * of the single big buffer. That means that every 'cut at the start' operation requires much less number of trailing symbols + * to be moved. Current constant defines default size of that small buffers. + */ + + private final int myCyclicBufferSize; + private final int myCyclicBufferUnitSize; + private final boolean myUseCyclicBuffer; + + /** + * Holds information about number of symbols stored at {@link #myDeferredOutput} collection. + */ + private int myDeferredOutputLength; + + /** Buffer for deferred stdin and stderr output. */ + private StringBuffer myDeferredUserInput = new StringBuffer(); + + /** + * Holds information about lexical division by offsets of the text that is not yet pushed to document. + *

+ * Target offsets are anchored to the {@link #myDeferredOutput deferred buffer}. + */ + private final List myDeferredTokens = new ArrayList(); + private final Set myDeferredTypes = new HashSet(); + + public ConsoleBuffer() { + this(useCycleBuffer(), getCycleBufferSize(), DEFAULT_CYCLIC_BUFFER_UNIT_SIZE); + } + + public ConsoleBuffer(boolean useCyclicBuffer, int cyclicBufferSize, int cyclicBufferUnitSize) { + myUseCyclicBuffer = useCyclicBuffer; + myCyclicBufferSize = cyclicBufferSize; + myCyclicBufferUnitSize = cyclicBufferUnitSize; + myContentTypesToNotStripOnCycling.add(ConsoleViewContentType.USER_INPUT); + } + + private static boolean useCycleBuffer() { + final String useCycleBufferProperty = System.getProperty("idea.cycle.buffer.size"); + return useCycleBufferProperty == null || !"disabled".equalsIgnoreCase(useCycleBufferProperty); + } + + private static int getCycleBufferSize() { + final String cycleBufferSizeProperty = System.getProperty("idea.cycle.buffer.size"); + if (cycleBufferSizeProperty == null) return 1024 * 1024; + try { + return Integer.parseInt(cycleBufferSizeProperty) * 1024; + } + catch (NumberFormatException e) { + return 1024 * 1024; + } + } + + public boolean isUseCyclicBuffer() { + return myUseCyclicBuffer; + } + + public int getCyclicBufferSize() { + return myCyclicBufferSize; + } + + public boolean isEmpty() { + return myDeferredOutput.isEmpty() || (myDeferredOutput.size() == 1 && myDeferredOutput.getFirst().length() <= 0); + } + + public int getLength() { + return myDeferredOutputLength; + } + + public int getUserInputLength() { + return myDeferredUserInput.length(); + } + + public String getUserInput() { + return myDeferredUserInput.toString(); + } + + public List getDeferredTokens() { + return myDeferredTokens; + } + + public Set getDeferredTokenTypes() { + return myDeferredTypes; + } + + public Deque getDeferredOutput() { + return myDeferredOutput; + } + + public String getText() { + if (myDeferredOutput.size() > 1) { + final StringBuilder buffer = new StringBuilder(); + for (StringBuilder builder : myDeferredOutput) { + buffer.append(builder); + } + return buffer.toString(); + } + else if (myDeferredOutput.size() == 1) { + return myDeferredOutput.getFirst().substring(0); + } + else { + return ""; + } + } + + /** + * This buffer automatically strips text that exceeds {@link #getCycleBufferSize() cyclic buffer size}. However, we may want + * to avoid 'significant text' stripping, i.e. don't strip the text of particular type. + *

+ * {@link ConsoleViewContentType#USER_INPUT} is considered to be such a type by default, however, it's possible to overwrite that + * via the current method. + * + * @param types content types that should not be stripped during the buffer's cycling + */ + public void setContentTypesToNotStripOnCycling(@NotNull Collection types) { + myContentTypesToNotStripOnCycling.clear(); + myContentTypesToNotStripOnCycling.addAll(types); + } + + public void clear() { + if (myUseCyclicBuffer) { + myDeferredOutput.clear(); + myDeferredOutput.add(new StringBuilder(myCyclicBufferUnitSize)); + } + else { + for (StringBuilder builder : myDeferredOutput) { + builder.setLength(0); + } + } + myDeferredOutputLength = 0; + myDeferredTypes.clear(); + myDeferredUserInput = new StringBuffer(); + myDeferredTokens.clear(); + } + + @Nullable + public String cutFirstUserInputLine() { + final String text = myDeferredUserInput.substring(0, myDeferredUserInput.length()); + final int index = Math.max(text.lastIndexOf('\n'), text.lastIndexOf('\r')); + if (index < 0) { + return null; + } + final String result = text.substring(0, index + 1); + myDeferredUserInput.setLength(0); + myDeferredUserInput.append(text.substring(index + 1)); + return result; + } + + public void addUserText(int offset, String text) { + myDeferredUserInput.insert(offset, text); + } + + public void removeUserText(int startOffset, int endOffset) { + myDeferredUserInput.delete(startOffset, endOffset); + } + + public void replaceUserText(int startOffset, int endOffset, String text) { + myDeferredUserInput.replace(startOffset, endOffset, text); + } + + /** + * Asks current buffer to store given text of the given type. + * + * @param s text to store + * @param contentType type of the given text + * @param info hyperlink info for the given text (if any) + * @return text that is actually stored (there is a possible case that the buffer is full and given text's type + * is considered to have lower priority than the stored one, hence, it's better to drop given text completely + * or partially) and number of existed symbols removed during storing the given data + */ + @NotNull + public Pair print(@NotNull String s, @NotNull ConsoleViewContentType contentType, @Nullable HyperlinkInfo info) { + int numberOfSymbolsToProceed = s.length(); + int trimmedSymbolsNumber = myDeferredOutputLength; + if (contentType != ConsoleViewContentType.USER_INPUT) { + numberOfSymbolsToProceed = trimDeferredOutputIfNecessary(s.length()); + trimmedSymbolsNumber -= myDeferredOutputLength; + } + else { + trimmedSymbolsNumber = 0; + } + + if (numberOfSymbolsToProceed <= 0) { + return new Pair("", 0); + } + + if (numberOfSymbolsToProceed < s.length()) { + s = s.substring(s.length() - numberOfSymbolsToProceed); + } + + myDeferredTypes.add(contentType); + + s = StringUtil.convertLineSeparators(s, !SystemInfo.isMac); + + myDeferredOutputLength += s.length(); + StringBuilder bufferToUse; + if (myDeferredOutput.isEmpty()) { + myDeferredOutput.add(bufferToUse = new StringBuilder(myCyclicBufferUnitSize)); + } + else { + bufferToUse = myDeferredOutput.getLast(); + } + int offset = 0; + while (offset < s.length()) { + if (bufferToUse.length() >= myCyclicBufferUnitSize) { + myDeferredOutput.add(bufferToUse = new StringBuilder(myCyclicBufferUnitSize)); + } + + if (bufferToUse.length() < myCyclicBufferUnitSize) { + int numberOfSymbolsToAdd = Math.min(myCyclicBufferUnitSize - bufferToUse.length(), s.length() - offset); + bufferToUse.append(s.substring(offset, offset + numberOfSymbolsToAdd)); + offset += numberOfSymbolsToAdd; + } + } + + if (contentType == ConsoleViewContentType.USER_INPUT) { + myDeferredUserInput.append(s); + } + + ConsoleUtil.addToken(s.length(), info, contentType, myDeferredTokens); + return new Pair(s, trimmedSymbolsNumber); + } + + //private void checkState() { + // int bufferOffset = 0; + // Iterator iterator = myDeferredOutput.iterator(); + // StringBuilder currentBuffer = null; + // int prevTokenEnd = 0; + // for (TokenInfo token : myDeferredTokens) { + // if (prevTokenEnd != token.startOffset) { + // try { + // System.out.println("Problem detected!"); + // System.in.read(); + // } + // catch (IOException e) { + // e.printStackTrace(); + // } + // } + // prevTokenEnd = token.endOffset; + // char c = token.contentType == ConsoleViewContentType.ERROR_OUTPUT ? '2' : '1'; + // int length = token.getLength(); + // if (currentBuffer == null) { + // currentBuffer = iterator.next(); + // } + // + // while (length > 0) { + // if (bufferOffset == currentBuffer.length()) { + // if (!iterator.hasNext()) { + // try { + // System.out.println("Problem detected!"); + // System.in.read(); + // } + // catch (IOException e) { + // e.printStackTrace(); + // } + // } + // currentBuffer = iterator.next(); + // bufferOffset = 0; + // } + // else { + // int endOffset = Math.min(bufferOffset + length, currentBuffer.length()); + // if (token.contentType == ConsoleViewContentType.NORMAL_OUTPUT || token.contentType == ConsoleViewContentType.ERROR_OUTPUT) { + // for (int i = bufferOffset; i < endOffset; i++) { + // char c1 = currentBuffer.charAt(i); + // if (c1 != c && c1 != '\n') { + // try { + // System.out.println("Problem detected!"); + // System.in.read(); + // } + // catch (IOException e) { + // e.printStackTrace(); + // } + // } + // } + // } + // length -= endOffset - bufferOffset; + // bufferOffset = endOffset; + // } + // } + // } + //} + + /** + * IJ console works as follows - it receives managed process outputs from dedicated thread that serves that process and + * pushes it to the {@link Document document} of editor used to represent process console. Important point here is that process + * output is received in a control flow of the thread over than EDT but push to the document is performed from EDT. Hence, we + * have a potential situation when particular process outputs a lot and EDT is busy or push to the document is performed slowly. + *

+ * We don't want to keep too many information from the underlying process then and want to trim text buffer that holds text + * to push to the document then. Current method serves exactly that purpose, i.e. it's expected to be called when new chunk of + * text is received from the underlying process and trims existing text buffer if necessary. + * + * @param numberOfNewSymbols number of symbols read from the managed process output + * @return number of newly read symbols that should be accepted + */ + @SuppressWarnings({"ForLoopReplaceableByForEach"}) + private int trimDeferredOutputIfNecessary(final int numberOfNewSymbols) { + if (!myUseCyclicBuffer || myDeferredOutputLength + numberOfNewSymbols <= myCyclicBufferSize) { + return numberOfNewSymbols; + } + + final int numberOfSymbolsToRemove = Math.min(myDeferredOutputLength, myDeferredOutputLength + numberOfNewSymbols - myCyclicBufferSize); + myDeferredTypes.clear(); + + if (DEBUG_PROCESSING) { + log("Starting console trimming. Need to delete %d symbols (deferred output length: %d, number of new symbols: %d, " + + "cyclic buffer size: %d). Current state:", + numberOfSymbolsToRemove, myDeferredOutputLength, numberOfNewSymbols, myCyclicBufferSize + ); + dumpDeferredOutput(); + } + + Context context = new Context(numberOfSymbolsToRemove); + + TIntArrayList indicesOfTokensToRemove = new TIntArrayList(); + for (int i = 0; i < myDeferredTokens.size(); i++) { + TokenInfo tokenInfo = myDeferredTokens.get(i); + tokenInfo.startOffset -= context.removedSymbolsNumber; + tokenInfo.endOffset -= context.removedSymbolsNumber; + + if (!context.canContinueProcessing()) { + // Just update token offsets. + myDeferredTypes.add(tokenInfo.contentType); + if (context.removedSymbolsNumber == 0) { + break; + } + continue; + } + + int tokenLength = tokenInfo.getLength(); + + // Don't remove input text. + if (myContentTypesToNotStripOnCycling.contains(tokenInfo.contentType)) { + skip(context, tokenLength); + myDeferredTypes.add(tokenInfo.contentType); + continue; + } + + int removedTokenSymbolsNumber = remove(context, tokenLength); + if (removedTokenSymbolsNumber == tokenLength) { + indicesOfTokensToRemove.add(i); + } + else { + tokenInfo.endOffset -= removedTokenSymbolsNumber; + myDeferredTypes.add(tokenInfo.contentType); + } + } + + for (int i = indicesOfTokensToRemove.size() - 1; i >= 0; i--) { + myDeferredTokens.remove(indicesOfTokensToRemove.get(i)); + } + + if (!myDeferredTokens.isEmpty()) { + TokenInfo tokenInfo = myDeferredTokens.get(0); + if (tokenInfo.startOffset > 0) { + final HyperlinkInfo hyperlinkInfo = tokenInfo.getHyperlinkInfo(); + myDeferredTokens.add(0, hyperlinkInfo != null ? new HyperlinkTokenInfo(ConsoleViewContentType.USER_INPUT, 0, tokenInfo.startOffset, hyperlinkInfo) + : new TokenInfo(ConsoleViewContentType.USER_INPUT, 0, tokenInfo.startOffset)); + myDeferredTypes.add(ConsoleViewContentType.USER_INPUT); + } + } + + if (numberOfNewSymbols + myDeferredOutputLength > myCyclicBufferSize) { + int result = myCyclicBufferSize - myDeferredOutputLength; + if (result < 0) { + return 0; + } + return result; + } + return numberOfNewSymbols; + } + + private static void skip(@NotNull Context context, int symbolsToSkipNumber) { + int remainingNumberOfBufferSymbols = context.currentBuffer.length() - context.bufferOffset; + if (remainingNumberOfBufferSymbols < symbolsToSkipNumber) { + symbolsToSkipNumber -= remainingNumberOfBufferSymbols; + while (context.iterator.hasNext()) { + context.currentBuffer = context.iterator.next(); + context.bufferOffset = 0; + if (DEBUG_PROCESSING) { + log("Switching to the next buffer. Number of token symbols to skip: %d", symbolsToSkipNumber); + } + if (symbolsToSkipNumber <= 0) { + break; + } + if (context.currentBuffer.length() > symbolsToSkipNumber) { + context.bufferOffset = symbolsToSkipNumber; + symbolsToSkipNumber = 0; + break; + } + else { + symbolsToSkipNumber -= context.currentBuffer.length(); + } + } + assert symbolsToSkipNumber <= 0; + } + else { + context.bufferOffset += symbolsToSkipNumber; + if (DEBUG_PROCESSING) { + log("All symbols to skip are processed. Current buffer offset is %d, text: '%s'", context.bufferOffset, context.currentBuffer); + } + } + } + + private int remove(@NotNull Context context, int tokenLength) { + int removedSymbolsNumber = 0; + int remainingTotalNumberOfSymbolsToRemove = context.numberOfSymbolsToRemove - context.removedSymbolsNumber; + int numberOfTokenSymbolsToRemove = Math.min(remainingTotalNumberOfSymbolsToRemove, tokenLength); + while (numberOfTokenSymbolsToRemove > 0 && context.currentBuffer != null) { + int diff = numberOfTokenSymbolsToRemove - (context.currentBuffer.length() - context.bufferOffset); + int endDeleteBufferOffset = Math.min(context.bufferOffset + numberOfTokenSymbolsToRemove, context.currentBuffer.length()); + int numberOfSymbolsRemovedFromCurrentBuffer = endDeleteBufferOffset - context.bufferOffset; + if (DEBUG_PROCESSING) { + log("About to delete %d symbols from the current buffer (offset is %d). Removed symbols number: %d. Current buffer: %d: '%s'", + numberOfSymbolsRemovedFromCurrentBuffer, context.bufferOffset, context.removedSymbolsNumber, context.currentBuffer.length(), + StringUtil.convertLineSeparators(context.currentBuffer.toString())); + } + numberOfTokenSymbolsToRemove -= numberOfSymbolsRemovedFromCurrentBuffer; + removedSymbolsNumber += numberOfSymbolsRemovedFromCurrentBuffer; + context.removedSymbolsNumber += numberOfSymbolsRemovedFromCurrentBuffer; + myDeferredOutputLength -= numberOfSymbolsRemovedFromCurrentBuffer; + + if (context.bufferOffset == 0 && (diff >= 0 || endDeleteBufferOffset == context.currentBuffer.length())) { + context.iterator.remove(); + context.nextBuffer(); + } + else { + context.currentBuffer.delete(context.bufferOffset, endDeleteBufferOffset); + if (DEBUG_PROCESSING) { + log("Removed symbols at range [%d; %d). Buffer offset: %d, buffer length: %d, text: '%s'", + context.bufferOffset, endDeleteBufferOffset, context.bufferOffset, context.currentBuffer.length(), context.currentBuffer); + } + if (context.bufferOffset == context.currentBuffer.length()) { + context.nextBuffer(); + } + } + } + return removedSymbolsNumber; + } + + private final class Context { + + public final int numberOfSymbolsToRemove; + public StringBuilder currentBuffer; + public Iterator iterator; + public int bufferOffset; + public int removedSymbolsNumber; + + Context(int numberOfSymbolsToRemove) { + this.numberOfSymbolsToRemove = numberOfSymbolsToRemove; + iterator = myDeferredOutput.iterator(); + if (iterator.hasNext()) { + currentBuffer = iterator.next(); + } + else { + currentBuffer = null; + } + } + + public boolean canContinueProcessing() { + return removedSymbolsNumber < numberOfSymbolsToRemove && currentBuffer != null; + } + + public boolean nextBuffer() { + if (iterator.hasNext()) { + currentBuffer = iterator.next(); + bufferOffset = 0; + return true; + } + return false; + } + } + + @SuppressWarnings({"PointlessBooleanExpression", "ConstantConditions"}) + private void dumpDeferredOutput() { + if (!DEBUG_PROCESSING) { + return; + } + log("Tokens:"); + for (TokenInfo token : myDeferredTokens) { + log("\t" + token); + } + log("Data:"); + for (StringBuilder buffer : myDeferredOutput) { + log("\t%d: '%s'", buffer.length(), StringUtil.convertLineSeparators(buffer.toString())); + } + log("-----------------------------------------------------------------------------------------------------"); + } + + @SuppressWarnings({"UnusedDeclaration", "CallToPrintStackTrace"}) + private static void log(Object o) { + //try { + // doLog(o); + //} + //catch (Exception e) { + // e.printStackTrace(); + //} + } + + @SuppressWarnings({"UnusedDeclaration", "CallToPrintStackTrace"}) + private static void log(String message, Object... formatData) { + //try { + // doLog(String.format(message, formatData)); + //} + //catch (Exception e) { + // e.printStackTrace(); + //} + } + + //private static BufferedWriter myWriter; + //private static void doLog(Object o) throws Exception { + // if (!DEBUG_PROCESSING) { + // return; + // } + // File file = new File("/home/denis/log/console.log"); + // if (myWriter == null || !file.exists()) { + // myWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file))); + // } + // myWriter.write(o.toString()); + // myWriter.newLine(); + // myWriter.flush(); + //} +} diff --git a/platform/lang-impl/src/com/intellij/execution/impl/ConsoleUtil.java b/platform/lang-impl/src/com/intellij/execution/impl/ConsoleUtil.java new file mode 100644 index 000000000000..4662a75c51f3 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/execution/impl/ConsoleUtil.java @@ -0,0 +1,54 @@ +/* + * Copyright 2000-2011 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 com.intellij.execution.impl; + +import static com.intellij.execution.impl.ConsoleViewImpl.TokenInfo; +import static com.intellij.execution.impl.ConsoleViewImpl.HyperlinkTokenInfo; + +import com.intellij.execution.filters.HyperlinkInfo; +import com.intellij.execution.ui.ConsoleViewContentType; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +/** + * Holds utility methods for console processing. + * + * @author Denis Zhdanov + * @since 4/6/11 3:50 PM + */ +public class ConsoleUtil { + + private ConsoleUtil() { + } + + public static void addToken(int length, @Nullable HyperlinkInfo info, ConsoleViewContentType contentType, List tokens) { + int startOffset = 0; + if (!tokens.isEmpty()) { + final TokenInfo lastToken = tokens.get(tokens.size() - 1); + if (lastToken.contentType == contentType && info == lastToken.getHyperlinkInfo()) { + lastToken.endOffset += length; // optimization + return; + } + else { + startOffset = lastToken.endOffset; + } + } + + tokens.add(info != null ? new HyperlinkTokenInfo(contentType, startOffset, startOffset + length, info) + : new TokenInfo(contentType, startOffset, startOffset + length)); + } +} diff --git a/platform/lang-impl/src/com/intellij/execution/impl/ConsoleViewImpl.java b/platform/lang-impl/src/com/intellij/execution/impl/ConsoleViewImpl.java index fabe15b66969..c410e9239c92 100644 --- a/platform/lang-impl/src/com/intellij/execution/impl/ConsoleViewImpl.java +++ b/platform/lang-impl/src/com/intellij/execution/impl/ConsoleViewImpl.java @@ -68,7 +68,7 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Key; -import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.LineTokenizer; import com.intellij.openapi.util.text.StringUtil; import com.intellij.pom.Navigatable; @@ -102,6 +102,7 @@ import java.util.List; import java.util.concurrent.CopyOnWriteArraySet; public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableConsoleView, DataProvider, OccurenceNavigator { + private @NonNls String CONSOLE_VIEW_POPUP_MENU = "ConsoleView.PopupMenu"; private static final Logger LOG = Logger.getInstance("#com.intellij.execution.impl.ConsoleViewImpl"); @@ -115,35 +116,6 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo typedAction.setupHandler(new MyTypedHandler(typedAction.getHandler())); } - private final int CYCLIC_BUFFER_SIZE = getCycleBufferSize(); - /** - * Main console usage scenario assumes the following: - *

-   * 
    - *
  • - * console may be {@link #print(String, ConsoleViewContentType) provided} with the new text from any thread - * (e.g. separate thread is charged for reading output of java application launched under IJ. That output is provided - * to the console); - *
  • - *
  • current class flushes provided text to {@link Editor editor} used for representing it to end-user from EDT;
  • - *
  • - * dedicated buffer is kept to hold console text between the moment when it's provided to the current class - * and flush to the editor;
  • - *
- *
- *

- * It's also possible to configure console to use cyclic buffer in order to avoid unnecessary memory consumption. - * However, that implies possibility of the following situation - console user provides it with the great number - * of small chunks of text (that is the case for junit processing). It's inappropriate to use single {@link StringBuilder} as - * a buffer then because every time we see that cyclic buffer size is exceeded and we need to cut exceeding text from buffer - * start, trailing part is moved to the zero offset. That produces extensive CPU usage in case of great number of small messages - * where every such message exceeds cyclic buffer size. - *

- * That is the reason why we use data structure similar to STL deque here - we hold number of string buffers of small size instead - * of the single big buffer. That means that every 'cut at the start' operation requires much less number of trailing symbols - * to be moved. Current constant defines desired size of that small buffers. - */ - private static final int CYCLIC_BUFFER_UNIT_SIZE = 256; private final CommandLineFolding myCommandLineFolding = new CommandLineFolding(); private final DisposedPsiManagerCheck myPsiDisposedCheck; @@ -153,30 +125,12 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo private Computable myStateForUpdate; - private static int getCycleBufferSize() { - final String cycleBufferSizeProperty = System.getProperty("idea.cycle.buffer.size"); - if (cycleBufferSizeProperty == null) return 1024 * 1024; - try { - return Integer.parseInt(cycleBufferSizeProperty) * 1024; - } - catch (NumberFormatException e) { - return 1024 * 1024; - } - } - - private final boolean USE_CYCLIC_BUFFER = useCycleBuffer(); - - private static boolean useCycleBuffer() { - final String useCycleBufferProperty = System.getProperty("idea.cycle.buffer.size"); - return useCycleBufferProperty == null || !"disabled".equalsIgnoreCase(useCycleBufferProperty); - } - private static final int HYPERLINK_LAYER = HighlighterLayer.SELECTION - 123; private final Alarm mySpareTimeAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD, this); private final CopyOnWriteArraySet myListeners = new CopyOnWriteArraySet(); - private final Set myDeferredTypes = new HashSet(); private final ArrayList customActions = new ArrayList(); + private final ConsoleBuffer myBuffer = new ConsoleBuffer(); @TestOnly public Editor getEditor() { @@ -211,10 +165,10 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo updateFoldings(0, myEditor.getDocument().getLineCount() - 1, true); } - private static class TokenInfo { - private final ConsoleViewContentType contentType; - private int startOffset; - private int endOffset; + static class TokenInfo { + final ConsoleViewContentType contentType; + int startOffset; + int endOffset; private final TextAttributes attributes; TokenInfo(final ConsoleViewContentType contentType, final int startOffset, final int endOffset) { @@ -239,7 +193,7 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo } } - private static class HyperlinkTokenInfo extends TokenInfo { + static class HyperlinkTokenInfo extends TokenInfo { private HyperlinkInfo myHyperlinkInfo; HyperlinkTokenInfo(final ConsoleViewContentType contentType, final int startOffset, final int endOffset, HyperlinkInfo hyperlinkInfo) { @@ -268,21 +222,6 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo */ private int myContentSize; - /** - * Buffer for deferred stdout, stderr and stdin output. - *

- * Feel free to check rationale for using this approach at {@link #CYCLIC_BUFFER_UNIT_SIZE} contract. - */ - private final Deque myDeferredOutput = new ArrayDeque(); - - /** - * Holds information about number of symbols stored at {@link #myDeferredOutput} collection. - */ - private int myDeferredOutputLength; - - /** Buffer for deferred stdin and stderr output. */ - private StringBuffer myDeferredUserInput = new StringBuffer(); - /** * Holds information about lexical division by offsets of the text already pushed to document. *

@@ -290,13 +229,6 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo */ private ArrayList myTokens = new ArrayList(); - /** - * Holds information about lexical division by offsets of the text that is not yet pushed to document. - *

- * Target offsets are anchored to the {@link #myDeferredOutput deferred buffer}. - */ - private final List myDeferredTokens = new ArrayList(); - private final Hyperlinks myHyperlinks = new Hyperlinks(); private final TIntObjectHashMap myFolding = new TIntObjectHashMap(); @@ -416,14 +348,8 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo // code and alarm clear request (in EDT) my occur print event in non-EDT thread // and unfortunately it is a real usecase and happens when switching active test in // tests console. - myContentSize = Math.max(0, myContentSize - myDeferredOutputLength); - - myDeferredOutput.clear(); - myDeferredOutput.add(new StringBuilder(CYCLIC_BUFFER_UNIT_SIZE)); - myDeferredOutputLength = 0; - myDeferredTypes.clear(); - myDeferredUserInput = new StringBuffer(); - myDeferredTokens.clear(); + myContentSize = Math.max(0, myContentSize - myBuffer.getLength()); + myBuffer.clear(); } myFlushAlarm.cancelAllRequests(); myFlushAlarm.addRequest(myClearRequest, 0, getStateForUpdate()); @@ -435,7 +361,7 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo flushDeferredText(false); if (myEditor == null) return; int moveOffset = offset; - if (USE_CYCLIC_BUFFER && moveOffset >= myEditor.getDocument().getTextLength()) { + if (myBuffer.isUseCyclicBuffer() && moveOffset >= myEditor.getDocument().getTextLength()) { moveOffset = 0; } myEditor.getCaretModel().moveToOffset(moveOffset); @@ -462,7 +388,7 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo public boolean hasDeferredOutput() { synchronized (LOCK) { - return myDeferredOutputLength > 0; + return myBuffer.getLength() > 0; } } @@ -531,9 +457,7 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo mySpareTimeAlarm.cancelAllRequests(); disposeEditor(); synchronized (LOCK) { - myDeferredOutput.clear(); - myDeferredOutput.add(new StringBuilder()); - myDeferredOutputLength = 0; + myBuffer.clear(); } myEditor = null; } @@ -551,44 +475,9 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo private void printHyperlink(String s, ConsoleViewContentType contentType, HyperlinkInfo info) { synchronized (LOCK) { - int numberOfSymbolsToProceed = s.length(); - if (contentType != ConsoleViewContentType.USER_INPUT) { - numberOfSymbolsToProceed = trimDeferredOutputIfNecessary(s.length()); - } - - if (numberOfSymbolsToProceed <= 0) { - return; - } - - if (numberOfSymbolsToProceed < s.length()) { - s = s.substring(s.length() - numberOfSymbolsToProceed); - } - - myDeferredTypes.add(contentType); - - s = StringUtil.convertLineSeparators(s, !SystemInfo.isMac); - myContentSize += s.length(); - myDeferredOutputLength += s.length(); - StringBuilder bufferToUse; - if (myDeferredOutput.isEmpty()) { - myDeferredOutput.add(bufferToUse = new StringBuilder(s.length())); - } - else { - StringBuilder lastBuffer = myDeferredOutput.getLast(); - bufferToUse = lastBuffer; - if (USE_CYCLIC_BUFFER) { - if (lastBuffer.length() + s.length() > CYCLIC_BUFFER_UNIT_SIZE) { - myDeferredOutput.addLast(bufferToUse = new StringBuilder(CYCLIC_BUFFER_UNIT_SIZE)); - } - } - } - - bufferToUse.append(s); - if (contentType == ConsoleViewContentType.USER_INPUT) { - myDeferredUserInput.append(s); - } - - addToken(s.length(), info, contentType, myDeferredTokens); + Pair pair = myBuffer.print(s, contentType, info); + s = pair.first; + myContentSize += s.length() - pair.second; if (s.indexOf('\n') >= 0 || s.indexOf('\r') >= 0) { if (contentType == ConsoleViewContentType.USER_INPUT) { @@ -596,146 +485,19 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo } } if (myFlushAlarm.getActiveRequestCount() == 0 && myEditor != null && !myFlushAlarm.isDisposed()) { - final boolean shouldFlushNow = USE_CYCLIC_BUFFER && myDeferredOutputLength > CYCLIC_BUFFER_SIZE; + final boolean shouldFlushNow = myBuffer.isUseCyclicBuffer() && myBuffer.getLength() >= myBuffer.getCyclicBufferSize(); myFlushAlarm.addRequest(myFlushDeferredRunnable, shouldFlushNow ? 0 : FLUSH_DELAY, getStateForUpdate()); } } } - /** - * IJ console works as follows - it receives managed process outputs from dedicated thread that serves that process and - * pushes it to the {@link Document document} of editor used to represent process console. Important point here is that process - * output is received in a control flow of the thread over than EDT but push to the document is performed from EDT. Hence, we - * have a potential situation when particular process outputs a lot and EDT is busy or push to the document is performed slowly. - *

- * We don't want to keep too many information from the underlying process then and want to trim text buffer that holds text - * to push to the document then. Current method serves exactly that purpose, i.e. it's expected to be called when new chunk of - * text is received from the underlying process and trims existing text buffer if necessary. - * - * @param numberOfNewSymbols number of symbols read from the managed process output - * @return number of newly read symbols that should be accepted - */ - @SuppressWarnings({"ForLoopReplaceableByForEach"}) - private int trimDeferredOutputIfNecessary(final int numberOfNewSymbols) { - if (!USE_CYCLIC_BUFFER || myDeferredOutputLength <= 0 || myDeferredOutputLength + numberOfNewSymbols <= CYCLIC_BUFFER_SIZE) { - return numberOfNewSymbols; - } - - final int numberOfSymbolsToRemove = myDeferredOutputLength + numberOfNewSymbols - CYCLIC_BUFFER_SIZE; - myDeferredTypes.clear(); - - int removedSymbolsNumber = 0; - int endIndex = -1; - Iterator iterator = myDeferredOutput.iterator(); - StringBuilder currentBuffer = iterator.next(); - int bufferOffset = 0; - for (int i = 0; i < myDeferredTokens.size(); i++) { - TokenInfo tokenInfo = myDeferredTokens.get(i); - - if (removedSymbolsNumber < numberOfSymbolsToRemove) { - int tokenLength = tokenInfo.getLength(); - - // Don't remove input text. - if (tokenInfo.contentType == ConsoleViewContentType.USER_INPUT) { - tokenInfo.startOffset -= removedSymbolsNumber; - tokenInfo.endOffset -= removedSymbolsNumber; - removedSymbolsNumber += tokenLength; - bufferOffset += tokenLength; - continue; - } - - int remainingNumberOfSymbolsToRemove = numberOfSymbolsToRemove - removedSymbolsNumber; - int numberOfSymbolsToRemoveNow = Math.min(remainingNumberOfSymbolsToRemove, tokenLength); - while (numberOfSymbolsToRemoveNow > 0) { - int diff = numberOfSymbolsToRemoveNow - currentBuffer.length() - bufferOffset; - int endDeleteOffset = Math.min(bufferOffset + numberOfSymbolsToRemoveNow, currentBuffer.length()); - int numberOfSymbolsRemovedFromCurrentBuffer = endDeleteOffset - bufferOffset; - numberOfSymbolsToRemoveNow -= numberOfSymbolsRemovedFromCurrentBuffer; - removedSymbolsNumber += numberOfSymbolsRemovedFromCurrentBuffer; - myDeferredOutputLength -= numberOfSymbolsRemovedFromCurrentBuffer; - currentBuffer.delete(bufferOffset, endDeleteOffset); - if (diff >= 0) { - iterator.remove(); - if (iterator.hasNext()) { - currentBuffer = iterator.next(); - } - else if (numberOfSymbolsToRemove > 0) { - assert false; - } - - bufferOffset = 0; - } - } - if (remainingNumberOfSymbolsToRemove < tokenLength) { - tokenInfo.startOffset += remainingNumberOfSymbolsToRemove; - endIndex = i; - } - else { - endIndex = i + 1; - } - } - - if (removedSymbolsNumber < numberOfSymbolsToRemove) { - continue; - } - - tokenInfo.startOffset -= numberOfSymbolsToRemove; - tokenInfo.endOffset -= numberOfSymbolsToRemove; - myDeferredTypes.add(tokenInfo.contentType); - } - - if (endIndex < 0) { - myDeferredTokens.clear(); - } - else if (endIndex > 0) { - myDeferredTokens.subList(0, endIndex).clear(); - } - - if (!myDeferredTokens.isEmpty()) { - TokenInfo tokenInfo = myDeferredTokens.get(0); - if (tokenInfo.startOffset > 0) { - final HyperlinkInfo hyperlinkInfo = tokenInfo.getHyperlinkInfo(); - myDeferredTokens.add(0, hyperlinkInfo != null ? new HyperlinkTokenInfo(ConsoleViewContentType.USER_INPUT, 0, tokenInfo.startOffset, hyperlinkInfo) - : new TokenInfo(ConsoleViewContentType.USER_INPUT, 0, tokenInfo.startOffset)); - myDeferredTypes.add(ConsoleViewContentType.USER_INPUT); - } - } - - myContentSize -= removedSymbolsNumber; - if (numberOfNewSymbols + myDeferredOutputLength > CYCLIC_BUFFER_SIZE) { - int result = CYCLIC_BUFFER_SIZE - myDeferredOutputLength; - if (result < 0) { - return 0; - } - return result; - } - return numberOfNewSymbols; - } - protected void beforeExternalAddContentToDocument(int length, ConsoleViewContentType contentType) { myContentSize+=length; addToken(length, null, contentType); } private void addToken(int length, @Nullable HyperlinkInfo info, ConsoleViewContentType contentType) { - addToken(length, info, contentType, myTokens); - } - - private static void addToken(int length, @Nullable HyperlinkInfo info, ConsoleViewContentType contentType, List tokens) { - int startOffset = 0; - if (!tokens.isEmpty()) { - final TokenInfo lastToken = tokens.get(tokens.size() - 1); - if (lastToken.contentType == contentType && info == lastToken.getHyperlinkInfo()) { - lastToken.endOffset += length; // optimization - return; - } - else { - startOffset = lastToken.endOffset; - } - } - - tokens.add(info != null ? new HyperlinkTokenInfo(contentType, startOffset, startOffset + length, info) - : new TokenInfo(contentType, startOffset, startOffset + length)); + ConsoleUtil.addToken(length, info, contentType, myTokens); } private ModalityState getStateForUpdate() { @@ -783,50 +545,29 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo }, null, DocCommandGroupId.noneGroupId(document)); } - final StringBuilder buffer = new StringBuilder(); + final String text; final Collection contentTypes; int deferredTokensSize; synchronized (LOCK) { if (myOutputPaused) return; - if (myDeferredOutput.isEmpty() || (myDeferredOutput.size() == 1 && myDeferredOutput.getFirst().length() <= 0)) return; + if (myBuffer.isEmpty()) return; if (myEditor == null) return; - if (myDeferredOutput.size() > 1) { - for (StringBuilder builder : myDeferredOutput) { - buffer.append(builder); - } - text = buffer.substring(0); - } - else if (myDeferredOutput.size() == 1) { - text = myDeferredOutput.getFirst().substring(0); - } - else { - return; - } + text = myBuffer.getText(); - contentTypes = Collections.unmodifiableCollection(new HashSet(myDeferredTypes)); - for (TokenInfo deferredToken : myDeferredTokens) { + contentTypes = Collections.unmodifiableCollection(new HashSet(myBuffer.getDeferredTokenTypes())); + List deferredTokens = myBuffer.getDeferredTokens(); + for (TokenInfo deferredToken : deferredTokens) { addToken(deferredToken.getLength(), deferredToken.getHyperlinkInfo(), deferredToken.contentType); } - deferredTokensSize = myDeferredTokens.size(); - myDeferredTokens.clear(); - myDeferredTypes.clear(); - if (USE_CYCLIC_BUFFER) { - myDeferredOutput.clear(); - myDeferredOutput.add(new StringBuilder(CYCLIC_BUFFER_SIZE)); - } - else { - for (StringBuilder builder : myDeferredOutput) { - builder.setLength(0); - } - } - myDeferredOutputLength = 0; + deferredTokensSize = deferredTokens.size(); + myBuffer.clear(); } final Document document = myEditor.getDocument(); final int oldLineCount = document.getLineCount(); final boolean isAtEndOfDocument = myEditor.getCaretModel().getOffset() == document.getTextLength(); - boolean cycleUsed = USE_CYCLIC_BUFFER && document.getTextLength() + text.length() > CYCLIC_BUFFER_SIZE; + boolean cycleUsed = myBuffer.isUseCyclicBuffer() && document.getTextLength() + text.length() > myBuffer.getCyclicBufferSize(); CommandProcessor.getInstance().executeCommand(myProject, new Runnable() { public void run() { int offset = myEditor.getCaretModel().getOffset(); @@ -887,12 +628,10 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo } private void flushDeferredUserInput() { - final String text = myDeferredUserInput.substring(0, myDeferredUserInput.length()); - final int index = Math.max(text.lastIndexOf('\n'), text.lastIndexOf('\r')); - if (index < 0) return; - final String textToSend = text.substring(0, index + 1); - myDeferredUserInput.setLength(0); - myDeferredUserInput.append(text.substring(index + 1)); + final String textToSend = myBuffer.cutFirstUserInputLine(); + if (textToSend == null) { + return; + } myFlushUserInputAlarm.addRequest(new Runnable() { public void run() { if (myState.isRunning()) { @@ -980,7 +719,7 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo }); - final int bufferSize = USE_CYCLIC_BUFFER ? CYCLIC_BUFFER_SIZE : 0; + final int bufferSize = myBuffer.isUseCyclicBuffer() ? myBuffer.getCyclicBufferSize() : 0; editor.getDocument().setCyclicBufferSize(bufferSize); editor.putUserData(CONSOLE_VIEW_IN_EDITOR_VIEW, this); @@ -1589,7 +1328,7 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo public void execute(final ConsoleViewImpl consoleView, final DataContext context) { final int maxSize = UISettings.getInstance().CONSOLE_COMMAND_HISTORY_LIMIT; synchronized (consoleView.LOCK) { - String str = consoleView.myDeferredUserInput.toString(); + String str = consoleView.myBuffer.getUserInput(); if (StringUtil.isNotEmpty(str)) { consoleView.myHistory.remove(str); consoleView.myHistory.add(str); @@ -1917,6 +1656,7 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo */ private void insertUserText(final String s, int offset) { final ConsoleViewImpl consoleView = this; + final ConsoleBuffer buffer = consoleView.myBuffer; final Editor editor = consoleView.myEditor; final Document document = editor.getDocument(); final int startOffset; @@ -1938,7 +1678,7 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo return; } - final int deferredOffset = myContentSize - consoleView.myDeferredOutputLength - consoleView.myDeferredUserInput.length(); + final int deferredOffset = myContentSize - buffer.getLength() - buffer.getUserInputLength(); if (offset > info.endOffset) { startOffset = info.endOffset; } @@ -1946,7 +1686,7 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo startOffset = Math.max(deferredOffset, Math.max(info.startOffset, offset)); } - consoleView.myDeferredUserInput.insert(startOffset - deferredOffset, s); + buffer.addUserText(startOffset - deferredOffset, s); int charCountToAdd = s.length(); info.endOffset += charCountToAdd; @@ -1972,6 +1712,7 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo return; } final ConsoleViewImpl consoleView = this; + final ConsoleBuffer buffer = consoleView.myBuffer; final Editor editor = consoleView.myEditor; final Document document = editor.getDocument(); final int startOffset; @@ -1987,9 +1728,9 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo editor.getSelectionModel().removeSelection(); return; } - if (consoleView.myDeferredUserInput.length() == 0) return; + if (buffer.isEmpty()) return; - final int deferredOffset = myContentSize - consoleView.myDeferredOutputLength - consoleView.myDeferredUserInput.length(); + final int deferredOffset = myContentSize - buffer.getLength() - buffer.getUserInputLength(); startOffset = getStartOffset(start, info, deferredOffset); endOffset = getEndOffset(end, info); @@ -2003,7 +1744,7 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo } int charCountToReplace = s.length() - endOffset + startOffset; - consoleView.myDeferredUserInput.replace(startOffset - deferredOffset, endOffset - deferredOffset, s); + buffer.replaceUserText(startOffset - deferredOffset, endOffset - deferredOffset, s); info.endOffset += charCountToReplace; if (info.startOffset == info.endOffset) { @@ -2026,6 +1767,7 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo */ private void deleteUserText(int offset, int length) { ConsoleViewImpl consoleView = this; + ConsoleBuffer buffer = consoleView.myBuffer; final Editor editor = consoleView.myEditor; final Document document = editor.getDocument(); final int startOffset; @@ -2035,9 +1777,9 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo if (consoleView.myTokens.isEmpty()) return; final TokenInfo info = consoleView.myTokens.get(consoleView.myTokens.size() - 1); if (info.contentType != ConsoleViewContentType.USER_INPUT) return; - if (consoleView.myDeferredUserInput.length() == 0) return; + if (myBuffer.getUserInputLength() == 0) return; - final int deferredOffset = myContentSize - consoleView.myDeferredOutputLength - consoleView.myDeferredUserInput.length(); + final int deferredOffset = myContentSize - buffer.getLength() - buffer.getUserInputLength(); startOffset = getStartOffset(offset, info, deferredOffset); endOffset = getEndOffset(offset + length, info); if (startOffset == -1 || @@ -2049,7 +1791,7 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo return; } - consoleView.myDeferredUserInput.delete(startOffset - deferredOffset, endOffset - deferredOffset); + buffer.removeUserText(startOffset - deferredOffset, endOffset - deferredOffset); int charCountToDelete = endOffset - startOffset; info.endOffset -= charCountToDelete; diff --git a/platform/lang-impl/src/com/intellij/execution/runners/AbstractConsoleRunnerWithHistory.java b/platform/lang-impl/src/com/intellij/execution/runners/AbstractConsoleRunnerWithHistory.java index fa7477da6cb0..01033af29063 100644 --- a/platform/lang-impl/src/com/intellij/execution/runners/AbstractConsoleRunnerWithHistory.java +++ b/platform/lang-impl/src/com/intellij/execution/runners/AbstractConsoleRunnerWithHistory.java @@ -51,7 +51,7 @@ import java.util.List; * This class provides basic functionality for running consoles. * It launches external process and handles line input with history */ -public abstract class AbstractConsoleRunnerWithHistory { +public abstract class AbstractConsoleRunnerWithHistory { private final Project myProject; private final String myConsoleTitle; @@ -59,7 +59,7 @@ public abstract class AbstractConsoleRunnerWithHistory { private final CommandLineArgumentsProvider myProvider; private final String myWorkingDir; - private LanguageConsoleViewImpl myConsoleView; + private T myConsoleView; private ConsoleExecuteActionHandler myConsoleExecuteActionHandler; @@ -194,7 +194,7 @@ public abstract class AbstractConsoleRunnerWithHistory { myConsoleView.getConsole().setEditable(false); } - protected abstract LanguageConsoleViewImpl createConsoleView(); + protected abstract T createConsoleView(); @Nullable protected abstract Process createProcess(CommandLineArgumentsProvider provider) throws ExecutionException; @@ -288,7 +288,7 @@ public abstract class AbstractConsoleRunnerWithHistory { } } - public LanguageConsoleViewImpl getConsoleView() { + public T getConsoleView() { return myConsoleView; } diff --git a/platform/lang-impl/src/com/intellij/find/editorHeaderActions/EditorHeaderToggleAction.java b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/EditorHeaderToggleAction.java index e8bd445d6f10..a4f246cc2be2 100644 --- a/platform/lang-impl/src/com/intellij/find/editorHeaderActions/EditorHeaderToggleAction.java +++ b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/EditorHeaderToggleAction.java @@ -27,6 +27,7 @@ public abstract class EditorHeaderToggleAction extends CheckboxAction { public JComponent createCustomComponent(Presentation presentation) { final JComponent customComponent = super.createCustomComponent(presentation); getCheckBox().setFocusable(false); + getCheckBox().setOpaque(false); return customComponent; } diff --git a/platform/lang-impl/src/com/intellij/find/editorHeaderActions/Utils.java b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/Utils.java index f3012fb7222d..4b1b0e7977dd 100644 --- a/platform/lang-impl/src/com/intellij/find/editorHeaderActions/Utils.java +++ b/platform/lang-impl/src/com/intellij/find/editorHeaderActions/Utils.java @@ -1,9 +1,14 @@ package com.intellij.find.editorHeaderActions; +import com.intellij.openapi.editor.Editor; import com.intellij.openapi.ui.popup.JBPopup; import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.ui.popup.PopupChooserBuilder; import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.vfs.ReadonlyStatusHandler; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiFile; import javax.swing.*; import java.awt.*; @@ -61,4 +66,20 @@ public class Utils { } } } + + public static boolean okToWrite(Editor e) { + final PsiFile psiFile = PsiDocumentManager.getInstance(e.getProject()).getPsiFile(e.getDocument()); + boolean okWritable; + if (psiFile != null) { + final VirtualFile virtualFile = psiFile.getVirtualFile(); + if (virtualFile != null) { + okWritable = ReadonlyStatusHandler.ensureFilesWritable(e.getProject(), virtualFile); + } else { + okWritable = psiFile.isWritable(); + } + } else { + okWritable = e.getDocument().isWritable(); + } + return okWritable; + } } diff --git a/platform/lang-impl/src/com/intellij/find/impl/livePreview/LivePreviewControllerBase.java b/platform/lang-impl/src/com/intellij/find/impl/livePreview/LivePreviewControllerBase.java index 098e18e69c8f..abb550bdd158 100644 --- a/platform/lang-impl/src/com/intellij/find/impl/livePreview/LivePreviewControllerBase.java +++ b/platform/lang-impl/src/com/intellij/find/impl/livePreview/LivePreviewControllerBase.java @@ -3,6 +3,7 @@ package com.intellij.find.impl.livePreview; import com.intellij.find.FindManager; import com.intellij.find.FindModel; import com.intellij.find.FindUtil; +import com.intellij.find.editorHeaderActions.Utils; import com.intellij.find.impl.FindResultImpl; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; @@ -194,7 +195,7 @@ public class LivePreviewControllerBase implements LivePreview.Delegate, FindUtil @Nullable @Override public TextRange performReplace(final LiveOccurrence occurrence, final String replacement, final Editor editor) { - if (myReplaceDenied) return null; + if (myReplaceDenied || !Utils.okToWrite(editor)) return null; TextRange range = occurrence.getPrimaryRange(); FindModel findModel = mySearchResults.getFindModel(); TextRange result = null; @@ -216,6 +217,7 @@ public class LivePreviewControllerBase implements LivePreview.Delegate, FindUtil @Override public void performReplaceAll(Editor e) { + if (!Utils.okToWrite(e)) return; if (mySearchResults.getFindModel() != null) { FindUtil.replace(e.getProject(), e, mySearchResults.getFindModel().isGlobal() ? 0 : mySearchResults.getEditor().getSelectionModel().getSelectionStart(), diff --git a/platform/lang-impl/src/com/intellij/find/replaceInProject/ReplaceInProjectManager.java b/platform/lang-impl/src/com/intellij/find/replaceInProject/ReplaceInProjectManager.java index 12e4999bdd6e..99583b9aad71 100644 --- a/platform/lang-impl/src/com/intellij/find/replaceInProject/ReplaceInProjectManager.java +++ b/platform/lang-impl/src/com/intellij/find/replaceInProject/ReplaceInProjectManager.java @@ -362,7 +362,7 @@ public class ReplaceInProjectManager { for (final Usage usage : selectedUsages) { final VirtualFile file = ((UsageInFile)usage).getFile(); - if (!file.isWritable()) { + if (file != null && !file.isWritable()) { if (readOnlyFiles == null) readOnlyFiles = new HashSet(); readOnlyFiles.add(file); } diff --git a/platform/lang-impl/src/com/intellij/openapi/editor/actions/IncrementalFindAction.java b/platform/lang-impl/src/com/intellij/openapi/editor/actions/IncrementalFindAction.java index 760d88f95feb..47a422c31efa 100644 --- a/platform/lang-impl/src/com/intellij/openapi/editor/actions/IncrementalFindAction.java +++ b/platform/lang-impl/src/com/intellij/openapi/editor/actions/IncrementalFindAction.java @@ -20,6 +20,7 @@ import com.intellij.find.EditorSearchComponent; import com.intellij.find.FindManager; import com.intellij.find.FindModel; import com.intellij.find.FindUtil; +import com.intellij.find.editorHeaderActions.Utils; import com.intellij.ide.DataManager; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.actionSystem.PlatformDataKeys; @@ -69,7 +70,7 @@ public class IncrementalFindAction extends EditorAction { public boolean isEnabled(Editor editor, DataContext dataContext) { Project project = PlatformDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(editor.getComponent())); - return project != null && !editor.isOneLineMode(); + return project != null && !editor.isOneLineMode() && (!myReplace || Utils.okToWrite(editor)); } } diff --git a/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/FileElementInfo.java b/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/FileElementInfo.java index 24fc5e010d83..54412d636905 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/FileElementInfo.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/FileElementInfo.java @@ -69,7 +69,7 @@ class FileElementInfo implements SmartPointerElementInfo { } @Override - public boolean pointsToTheSameElementAs(SmartPointerElementInfo other) { + public boolean pointsToTheSameElementAs(@NotNull SmartPointerElementInfo other) { if (other instanceof FileElementInfo) { return myVirtualFile == ((FileElementInfo)other).myVirtualFile; } diff --git a/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/HardElementInfo.java b/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/HardElementInfo.java index 0573a30cc0bb..50c5ff8b32cd 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/HardElementInfo.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/HardElementInfo.java @@ -65,7 +65,7 @@ class HardElementInfo implements SmartPointerElementInfo { } @Override - public boolean pointsToTheSameElementAs(SmartPointerElementInfo other) { + public boolean pointsToTheSameElementAs(@NotNull SmartPointerElementInfo other) { return Comparing.equal(myElement, other.restoreElement()); } diff --git a/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/InjectedSelfElementInfo.java b/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/InjectedSelfElementInfo.java index a37520cc773d..2535d73ebfa0 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/InjectedSelfElementInfo.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/InjectedSelfElementInfo.java @@ -17,24 +17,73 @@ package com.intellij.psi.impl.smartPointers; import com.intellij.lang.injection.InjectedLanguageManager; import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.Segment; +import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.*; import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; import org.jetbrains.annotations.NotNull; +import java.util.List; + /** * User: cdr */ class InjectedSelfElementInfo extends SelfElementInfo { + private final SmartPsiFileRange myPsiFileRangeInHostElement; + private final Class myClass; + InjectedSelfElementInfo(@NotNull Project project, @NotNull PsiElement anchor, - @NotNull PsiFile containingFile) { - super(project, InjectedLanguageManager.getInstance(project).injectedToHost(anchor, anchor.getTextRange()), anchor.getClass(), InjectedLanguageUtil.getTopLevelFile(containingFile)); - assert containingFile.getContext() != null; + @NotNull PsiElement context) { + super(project, context); + SmartPointerManager smartPointerManager = SmartPointerManager.getInstance(project); + myPsiFileRangeInHostElement = smartPointerManager.createSmartPsiFileRangePointer(context.getContainingFile(), InjectedLanguageManager.getInstance( + project).injectedToHost(anchor, anchor.getTextRange())); + myClass = anchor.getClass(); } @Override - protected PsiElement findAnchorAt(@NotNull PsiFile file, int syncStartOffset) { - return InjectedLanguageUtil.findInjectedElementNoCommitWithOffset(file, syncStartOffset); + public VirtualFile getVirtualFile() { + PsiElement element = restoreElement(); + if (element == null) return null; + return element.getContainingFile().getVirtualFile(); + } + + @Override + public PsiElement restoreElement() { + PsiElement host = super.restoreElement(); + if (host == null) return null; + + Segment segment = myPsiFileRangeInHostElement.getRange(); + if (segment == null) return null; + final TextRange rangeInHostElement = TextRange.create(segment); + final Ref result = new Ref(); + + InjectedLanguageUtil.enumerate(host, host.getContainingFile(), new PsiLanguageInjectionHost.InjectedPsiVisitor() { + @Override + public void visit(@NotNull PsiFile injectedPsi, @NotNull List places) { + if (result.get() != null) return; + TextRange hostRange = + InjectedLanguageManager.getInstance(getProject()).injectedToHost(injectedPsi, new TextRange(0, injectedPsi.getTextLength())); + if (hostRange.contains(rangeInHostElement)) { + TextRange rangeInside = rangeInHostElement.shiftRight(-hostRange.getStartOffset()); + PsiElement element = findElementInside(injectedPsi, rangeInside.getStartOffset(), rangeInside.getEndOffset(), myClass); + result.set(element); + } + } + }, false); + + return result.get(); + } + + @Override + public boolean pointsToTheSameElementAs(@NotNull SmartPointerElementInfo other) { + if (getClass() != other.getClass()) return false; + if (!super.pointsToTheSameElementAs(other)) return false; + SmartPointerElementInfo myElementInfo = ((SmartPsiElementPointerImpl)myPsiFileRangeInHostElement).getElementInfo(); + SmartPointerElementInfo oElementInfo = ((SmartPsiElementPointerImpl)((InjectedSelfElementInfo)other).myPsiFileRangeInHostElement).getElementInfo(); + return myElementInfo.pointsToTheSameElementAs(oElementInfo); } } diff --git a/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/SelfElementInfo.java b/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/SelfElementInfo.java index 6aef9bb78e8b..f347c6b0cd15 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/SelfElementInfo.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/SelfElementInfo.java @@ -34,7 +34,7 @@ import java.lang.ref.SoftReference; * User: cdr */ public class SelfElementInfo implements SmartPointerElementInfo { - protected final VirtualFile myVirtualFile; + private final VirtualFile myVirtualFile; private Reference myMarkerRef; // create marker only in case of live document private int mySyncStartOffset; private int mySyncEndOffset; @@ -44,6 +44,9 @@ public class SelfElementInfo implements SmartPointerElementInfo { @SuppressWarnings({"UnusedDeclaration"}) private RangeMarker myRangeMarker; //maintain hard reference during modification + protected SelfElementInfo(@NotNull Project project, @NotNull PsiElement anchor) { + this(project, anchor.getTextRange(), anchor.getClass(), anchor.getContainingFile()); + } public SelfElementInfo(@NotNull Project project, @NotNull TextRange anchor, @NotNull Class anchorClass, @NotNull PsiFile containingFile) { myVirtualFile = containingFile.getVirtualFile(); myType = anchorClass; @@ -154,7 +157,11 @@ public class SelfElementInfo implements SmartPointerElementInfo { final int syncStartOffset = getSyncStartOffset(); final int syncEndOffset = getSyncEndOffset(); - PsiElement anchor = findAnchorAt(file, syncStartOffset); + return findElementInside(file, syncStartOffset, syncEndOffset, myType); + } + + protected static PsiElement findElementInside(PsiFile file, int syncStartOffset, int syncEndOffset, Class type) { + PsiElement anchor = file.getViewProvider().findElementAt(syncStartOffset, file.getLanguage()); if (anchor == null) return null; TextRange range = anchor.getTextRange(); @@ -166,7 +173,7 @@ public class SelfElementInfo implements SmartPointerElementInfo { range = anchor.getTextRange(); } - while (range.getEndOffset() == syncEndOffset && anchor != null && !myType.equals(anchor.getClass())) { + while (range.getEndOffset() == syncEndOffset && anchor != null && !type.equals(anchor.getClass())) { anchor = anchor.getParent(); if (anchor == null || anchor.getTextRange() == null) break; range = anchor.getTextRange(); @@ -176,10 +183,6 @@ public class SelfElementInfo implements SmartPointerElementInfo { return null; } - protected PsiElement findAnchorAt(@NotNull PsiFile file, int syncStartOffset) { - return file.getViewProvider().findElementAt(syncStartOffset, file.getLanguage()); - } - @Override public void dispose() { RangeMarker marker = getMarker(); @@ -251,7 +254,7 @@ public class SelfElementInfo implements SmartPointerElementInfo { } @Override - public boolean pointsToTheSameElementAs(SmartPointerElementInfo other) { + public boolean pointsToTheSameElementAs(@NotNull SmartPointerElementInfo other) { if (other instanceof SelfElementInfo) { return myVirtualFile == ((SelfElementInfo)other).myVirtualFile && myType == ((SelfElementInfo)other).myType diff --git a/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerElementInfo.java b/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerElementInfo.java index 8ef474f6dd1e..ef8b6afda90f 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerElementInfo.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerElementInfo.java @@ -38,7 +38,7 @@ interface SmartPointerElementInfo { void dispose(); int elementHashCode(); // must be immutable - boolean pointsToTheSameElementAs(SmartPointerElementInfo other); + boolean pointsToTheSameElementAs(@NotNull SmartPointerElementInfo other); VirtualFile getVirtualFile(); diff --git a/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/SmartPsiElementPointerImpl.java b/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/SmartPsiElementPointerImpl.java index 82c8736f34dd..7401a1664a07 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/SmartPsiElementPointerImpl.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/SmartPsiElementPointerImpl.java @@ -85,7 +85,7 @@ class SmartPsiElementPointerImpl implements SmartPointerEx if (element != null && !element.isValid()) { element = null; } - if (element == null && myElementInfo != null) { + if (element == null) { element = (E)myElementInfo.restoreElement(); if (element != null && (!element.getClass().equals(myElementClass) || !element.isValid())) { element = null; @@ -153,7 +153,8 @@ class SmartPsiElementPointerImpl implements SmartPointerEx FileViewProvider viewProvider = containingFile.getViewProvider(); if (viewProvider instanceof InjectedFileViewProvider) { - return new InjectedSelfElementInfo(project, element, containingFile); + PsiElement context = containingFile.getContext(); + if (context != null) return new InjectedSelfElementInfo(project, element, context); } if (element instanceof PsiFile) { @@ -167,17 +168,13 @@ class SmartPsiElementPointerImpl implements SmartPointerEx } public void documentAndPsiInSync() { - if (myElementInfo != null) { - myElementInfo.documentAndPsiInSync(); - } + myElementInfo.documentAndPsiInSync(); } @Override public void dispose() { - if (myElementInfo != null) { - myElementInfo.dispose(); - myElement = null; - } + myElementInfo.dispose(); + myElement = null; } @Override @@ -189,6 +186,7 @@ class SmartPsiElementPointerImpl implements SmartPointerEx myElementInfo.fastenBelt(offset); } + @NotNull SmartPointerElementInfo getElementInfo() { return myElementInfo; } diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/PostprocessReformattingAspect.java b/platform/lang-impl/src/com/intellij/psi/impl/source/PostprocessReformattingAspect.java index 49e2c6ba8c26..b96bcdc8ea63 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/PostprocessReformattingAspect.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/PostprocessReformattingAspect.java @@ -221,7 +221,7 @@ public class PostprocessReformattingAspect implements PomModelAspect, Disposable LOG.error(e); } finally { - LOG.assertTrue(myReformatElements.isEmpty()); + LOG.assertTrue(myReformatElements.isEmpty(), myReformatElements); } } }); diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeEditUtil.java b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeEditUtil.java index f3f17275ffb5..a5fc25fa03bc 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeEditUtil.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeEditUtil.java @@ -22,13 +22,11 @@ import com.intellij.formatting.FormattingModelBuilder; import com.intellij.formatting.IndentInfo; import com.intellij.lang.*; import com.intellij.openapi.command.AbnormalCommandTerminationException; +import com.intellij.openapi.editor.Document; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.TextRange; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiManager; -import com.intellij.psi.TokenType; +import com.intellij.psi.*; import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.psi.codeStyle.CodeStyleSettingsManager; import com.intellij.psi.impl.source.tree.Factory; @@ -53,7 +51,13 @@ public class CodeEditUtil { return Boolean.TRUE; } }; - + private static final ThreadLocal ALLOW_NODES_REFORMATTING = new ThreadLocal() { + @Override + protected Boolean initialValue() { + return Boolean.TRUE; + } + }; + public static final Key OUTER_OK = new Key("OUTER_OK"); private CodeEditUtil() { @@ -425,7 +429,7 @@ public class CodeEditUtil { * @return true if given node is configured to be reformatted; false otherwise */ public static boolean isMarkedToReformat(final ASTNode node) { - return node.getCopyableUserData(REFORMAT_KEY) != null; + return node.getCopyableUserData(REFORMAT_KEY) != null && ALLOW_NODES_REFORMATTING.get(); } /** @@ -454,9 +458,9 @@ public class CodeEditUtil { * to {@link #markToReformat(ASTNode, boolean) mark node for postponed formatting} will have no effect until current method is * called with 'true' as an argument. Hence, following usage scenario is expected: *

    - *
  1. this method is called with 'false' argument;
  2. - *
  3. formatting is performed at dedicated 'try' block;
  4. - *
  5. this method is called with 'false' argument from 'finally' section;
  6. + *
  7. This method is called with 'false' argument;
  8. + *
  9. Formatting is performed at dedicated 'try' block;
  10. + *
  11. This method is called with 'false' argument from 'finally' section;
  12. *
* * @param allow flag that defines if new reformat markers can be added from the current thread @@ -465,4 +469,32 @@ public class CodeEditUtil { public static void allowToMarkNodesForPostponedFormatting(boolean allow) { ALLOW_TO_MARK_NODES_TO_REFORMAT.set(allow); } + + /** + * @return 'allow suspended formatting' flag value + * @see #setAllowSuspendNodesReformatting(boolean) + */ + public static boolean isSuspendedNodesReformattingAllowed() { + return ALLOW_NODES_REFORMATTING.get(); + } + + /** + * There is a possible case that particular PSI tree node is {@link #markToReformat(ASTNode, boolean) marked for reformatting}. + * That means that there is a big chance that the node will be re-formatted during corresponding document processing + * (e.g. on call to {@link PsiDocumentManager#doPostponedOperationsAndUnblockDocument(Document)}). + *

+ * However, there is a possible case that particular activity that triggers such document processing is not ready to the + * situation when the document is modified because of postponed formatting. Hence, it may ask to suspend postponed formatting + * for a while. This method allows to do that at thread-local manner. I.e. it's expected to be called as follows: + *

+   * 
    + *
  1. This method is called with 'false' argument;
  2. + *
  3. Document is processed at dedicated 'try' block;
  4. + *
  5. This method is called with 'false' argument from 'finally' section;
  6. + *
+
+ */ + public static void setAllowSuspendNodesReformatting(boolean allow) { + ALLOW_NODES_REFORMATTING.set(allow); + } } diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/FileReference.java b/platform/lang-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/FileReference.java index 978638d19aa7..0778acf2efb6 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/FileReference.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/FileReference.java @@ -202,7 +202,7 @@ public class FileReference implements FileReferenceOwner, PsiPolyVariantReferenc public boolean execute(PsiFileSystemItem element) { result.add(new PsiElementResolveResult(getOriginalFile(element))); - return false; + return true; } }); } diff --git a/platform/lang-impl/testSrc/com/intellij/execution/impl/ConsoleBufferTest.java b/platform/lang-impl/testSrc/com/intellij/execution/impl/ConsoleBufferTest.java new file mode 100644 index 000000000000..b7be92d06560 --- /dev/null +++ b/platform/lang-impl/testSrc/com/intellij/execution/impl/ConsoleBufferTest.java @@ -0,0 +1,148 @@ +package com.intellij.execution.impl; + +import com.intellij.execution.ui.ConsoleViewContentType; +import com.intellij.openapi.editor.markup.TextAttributes; +import org.jetbrains.annotations.NotNull; +import org.junit.Before; +import org.junit.Test; + +import java.util.*; + +import static com.intellij.execution.impl.ConsoleViewImpl.TokenInfo; +import static org.junit.Assert.assertEquals; + +/** + * @author Denis Zhdanov + * @since 04/06/2011 + */ +public class ConsoleBufferTest { + + private static final ConsoleViewContentType IMPORTANT_OUTPUT = new ConsoleViewContentType("IMPORTANT_OUTPUT", (TextAttributes)null); + private static final ConsoleViewContentType NORMAL_OUTPUT = new ConsoleViewContentType("NORMAL_OUTPUT", (TextAttributes)null); + private static final ConsoleViewContentType BORING_OUTPUT = new ConsoleViewContentType("BORING_OUTPUT", (TextAttributes)null); + + private ConsoleBuffer myBuffer; + private int myBufferSize; + private int myBufferUnitSize; + + @Before + public void setUp() throws Exception { + myBufferSize = 10; + myBufferUnitSize = 3; + init(); + } + + @Test + public void nonExceedingBufferSize() { + myBuffer.print("a", NORMAL_OUTPUT, null); + checkState(s("a"), new TokenInfo(NORMAL_OUTPUT, 0, 1)); + + myBuffer.print("b", BORING_OUTPUT, null); + checkState(s("ab"), new TokenInfo(NORMAL_OUTPUT, 0, 1), new TokenInfo(BORING_OUTPUT, 1, 2)); + + myBuffer.print("cd", BORING_OUTPUT, null); + checkState(s("abc", "d"), new TokenInfo(NORMAL_OUTPUT, 0, 1), new TokenInfo(BORING_OUTPUT, 1, 4)); + + myBuffer.print("ef", BORING_OUTPUT, null); + checkState(s("abc", "def"), new TokenInfo(NORMAL_OUTPUT, 0, 1), new TokenInfo(BORING_OUTPUT, 1, 6)); + + myBuffer.print("ghij", NORMAL_OUTPUT, null); + checkState( + s("abc", "def", "ghi", "j"), + new TokenInfo(NORMAL_OUTPUT, 0, 1), new TokenInfo(BORING_OUTPUT, 1, 6), new TokenInfo(NORMAL_OUTPUT, 6, 10) + ); + } + + @Test + public void cycling() { + myBuffer.print("abcdefghi", NORMAL_OUTPUT, null); + myBuffer.print("jklm", BORING_OUTPUT, null); + checkState(s("def", "ghi", "jkl", "m"), new TokenInfo(NORMAL_OUTPUT, 0, 6), new TokenInfo(BORING_OUTPUT, 6, 10)); + } + + @Test + public void removedTokenOnCyclingIsNotShownAtDeferredTypes() { + myBuffer.print("a", BORING_OUTPUT, null); + myBuffer.print("bcdefghijklmn", NORMAL_OUTPUT, null); + checkState(s("efg", "hij", "klm", "n"), new TokenInfo(NORMAL_OUTPUT, 0, 10)); + } + + @Test + public void tokenDataIsTrimmed() { + myBuffer.print("1234567890", NORMAL_OUTPUT, null); + myBuffer.print("123456789012", NORMAL_OUTPUT, null); + checkState(s("345", "678", "901", "2"), new TokenInfo(NORMAL_OUTPUT, 0, 10)); + } + + @Test + public void removingOfGreatNumberOfTokens() { + myBuffer.print("a", NORMAL_OUTPUT, null); + myBuffer.print("b", BORING_OUTPUT, null); + myBuffer.print("a", NORMAL_OUTPUT, null); + myBuffer.print("b", BORING_OUTPUT, null); + myBuffer.print("a", NORMAL_OUTPUT, null); + myBuffer.print("1234567890", BORING_OUTPUT, null); + + checkState(s("123", "456", "789", "0"), new TokenInfo(BORING_OUTPUT, 0, 10)); + } + + @Test + public void removeFromBufferEnd() { + myBuffer.print("1234567", IMPORTANT_OUTPUT, null); + myBuffer.print("890", NORMAL_OUTPUT, null); + myBuffer.print("abc", BORING_OUTPUT, null); + + checkState( + s("123", "456", "7ab", "c"), + new TokenInfo(IMPORTANT_OUTPUT, 0, 7), new TokenInfo(BORING_OUTPUT, 7, 10) + ); + } + + @Test + public void smallRemoveAtStart() { + myBuffer.print("abc", NORMAL_OUTPUT, null); + myBuffer.print("def", BORING_OUTPUT, null); + myBuffer.print("ghi", NORMAL_OUTPUT, null); + myBuffer.print("k", BORING_OUTPUT, null); + myBuffer.print("l", NORMAL_OUTPUT, null); + + checkState( + s("bc", "def", "ghi", "kl"), + new TokenInfo(NORMAL_OUTPUT, 0, 2), new TokenInfo(BORING_OUTPUT, 2, 5), new TokenInfo(NORMAL_OUTPUT, 5, 8), + new TokenInfo(BORING_OUTPUT, 8, 9), new TokenInfo(NORMAL_OUTPUT, 9, 10) + ); + } + + private static List s(String ... strings) { + return Arrays.asList(strings); + } + + private void checkState(@NotNull List expectedBuffers, @NotNull TokenInfo ... expectedTokens) { + Deque actualBuffers = myBuffer.getDeferredOutput(); + assertEquals(expectedBuffers.size(), actualBuffers.size()); + int i = 0; + for (StringBuilder actual : actualBuffers) { + String expected = expectedBuffers.get(i++); + assertEquals(expected, actual.toString()); + } + + List actualTokens = myBuffer.getDeferredTokens(); + assertEquals(expectedTokens.length, actualTokens.size()); + i = 0; + Set contentTypes = new HashSet(); + for (TokenInfo actual : actualTokens) { + TokenInfo expected = expectedTokens[i++]; + assertEquals(expected.contentType, actual.contentType); + assertEquals(expected.startOffset, actual.startOffset); + assertEquals(expected.endOffset, actual.endOffset); + contentTypes.add(expected.contentType); + } + + assertEquals(contentTypes, myBuffer.getDeferredTokenTypes()); + } + + private void init() throws Exception { + myBuffer = new ConsoleBuffer(true, myBufferSize, myBufferUnitSize); + myBuffer.setContentTypesToNotStripOnCycling(Collections.singleton(IMPORTANT_OUTPUT)); + } +} diff --git a/platform/platform-api/src/com/intellij/openapi/editor/GenericLineWrapPositionStrategy.java b/platform/platform-api/src/com/intellij/openapi/editor/GenericLineWrapPositionStrategy.java index e2151c99d906..84b89cf27870 100644 --- a/platform/platform-api/src/com/intellij/openapi/editor/GenericLineWrapPositionStrategy.java +++ b/platform/platform-api/src/com/intellij/openapi/editor/GenericLineWrapPositionStrategy.java @@ -71,7 +71,7 @@ public class GenericLineWrapPositionStrategy implements LineWrapPositionStrategy if (rule.condition == WrapCondition.BOTH || rule.condition == WrapCondition.AFTER) { int target = i+1; if (rule.symbol != ' ') { - while(i < maxPreferredOffsetToUse && text.charAt(target) == ' ') { + while(target < maxPreferredOffsetToUse && text.charAt(target) == ' ') { target++; } } diff --git a/platform/platform-api/src/com/intellij/openapi/fileEditor/FileDocumentManager.java b/platform/platform-api/src/com/intellij/openapi/fileEditor/FileDocumentManager.java index f0181c23c35a..243ad6dd4cfd 100644 --- a/platform/platform-api/src/com/intellij/openapi/fileEditor/FileDocumentManager.java +++ b/platform/platform-api/src/com/intellij/openapi/fileEditor/FileDocumentManager.java @@ -27,6 +27,7 @@ public abstract class FileDocumentManager { return ApplicationManager.getApplication().getComponent(FileDocumentManager.class); } + @Nullable public abstract Document getDocument(@NotNull VirtualFile file); @Nullable diff --git a/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.java b/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.java index f222cd4772cd..8c5f4f3a1449 100644 --- a/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.java +++ b/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.java @@ -434,6 +434,9 @@ public class IdeEventQueue extends EventQueue { if (e instanceof KeyEvent) { myKeyboardBusy = e.getID() != KeyEvent.KEY_RELEASED || ((KeyEvent)e).getModifiers() != 0; + if (e.getID() == KeyEvent.KEY_RELEASED && ((KeyEvent)e).getKeyCode() == KeyEvent.VK_SHIFT) { + myMouseEventDispatcher.resetHorScrollingTracker(); + } } if (!typeaheadFlushing && typeAheadDispatchToFocusManager(e)) return; diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/EditorUtil.java b/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/EditorUtil.java index 070e853c27b8..3e023d3a7f85 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/EditorUtil.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/EditorUtil.java @@ -30,12 +30,13 @@ import org.jetbrains.annotations.NotNull; import java.awt.*; import java.awt.event.MouseWheelEvent; +import java.util.Arrays; import java.util.List; public class EditorUtil { private EditorUtil() { } - public static int getLastVisualLineColumnNumber(Editor editor, int line) { + public static int getLastVisualLineColumnNumber(Editor editor, final int line) { Document document = editor.getDocument(); int lastLine = document.getLineCount() - 1; if (lastLine < 0) { @@ -128,8 +129,9 @@ public class EditorUtil { assert false : String.format( "Target visual line: %d, mapped logical line: %d, visual lines range for the mapped logical line: [%s]-[%s], soft wraps for " - + "the target logical line: %s%nDocument text: '%s'", - line, resultLogLine, resVisStart, resVisEnd, softWraps, editor.getDocument().getText() + + "the target logical line: %s, all soft wraps: %s, fold regions: %s%nDocument text: '%s'", + line, resultLogLine, resVisStart, resVisEnd, softWraps, editor.getSoftWrapModel().getSoftWrapsForRange(0, document.getTextLength()), + Arrays.toString(editor.getFoldingModel().getAllFoldRegions()), editor.getDocument().getText() ); return resVisEnd.column; } @@ -372,7 +374,9 @@ public class EditorUtil { for (int i = start; i < offset; i++) { char c = text.charAt(i); - assert c != '\n' && c != '\r'; + assert c != '\n' && c != '\r' : + String.format("Symbol: %c, its index: %d, given start: %d, given offset: %d, given tab size: %d, text: '%s'", + c, i, start, offset, tabSize, text); if (c == '\t') { shift += getTabLength(i + shift - start, tabSize) - 1; } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java index 579aa575dcef..a048f2cf7cc0 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java @@ -635,6 +635,7 @@ public class SoftWrapModelImpl implements SoftWrapModelEx, PrioritizedDocumentLi + "%s%nDocument:%n%s%nFold regions: %s", task, myDataMapper, myEditor.getDocument().getText(), Arrays.toString(myEditor.getFoldingModel().fetchTopLevel())), e); } + myEditor.getFoldingModel().rebuild(); myDataMapper.release(); myApplianceManager.reset(); myStorage.removeAll(); diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileDocumentManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileDocumentManagerImpl.java index e4dae40553d6..294f15809460 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileDocumentManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileDocumentManagerImpl.java @@ -107,6 +107,7 @@ public class FileDocumentManagerImpl extends FileDocumentManager implements Appl public void disposeComponent() { } + @Nullable public Document getDocument(@NotNull final VirtualFile file) { DocumentEx document = (DocumentEx)getCachedDocument(file); if (document == null) { diff --git a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/IdeMouseEventDispatcher.java b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/IdeMouseEventDispatcher.java index 8626acdb780c..2d9d3f65a19d 100644 --- a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/IdeMouseEventDispatcher.java +++ b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/IdeMouseEventDispatcher.java @@ -47,6 +47,7 @@ public final class IdeMouseEventDispatcher { private final PresentationFactory myPresentationFactory = new PresentationFactory(); private final ArrayList myActions = new ArrayList(1); private final Map myRootPane2BlockedId = new HashMap(); + private int myLastHorScrolledComponentHash = 0; public IdeMouseEventDispatcher() { } @@ -187,16 +188,23 @@ public final class IdeMouseEventDispatcher { return false; } - private static boolean doHorizontalScrolling(Component c, MouseWheelEvent me) { - FeatureUsageTracker.getInstance().triggerFeatureUsed("ui.horizontal.scrolling"); + private boolean doHorizontalScrolling(Component c, MouseWheelEvent me) { final JScrollBar scrollBar = findHorizontalScrollBar(c); if (scrollBar != null) { + if (scrollBar.hashCode() != myLastHorScrolledComponentHash) { + FeatureUsageTracker.getInstance().triggerFeatureUsed("ui.horizontal.scrolling"); + myLastHorScrolledComponentHash = scrollBar.hashCode(); + } scrollBar.setValue(scrollBar.getValue() + getScrollAmount(c, me, scrollBar)); return true; } return false; } + public void resetHorScrollingTracker() { + myLastHorScrolledComponentHash = 0; + } + private static int getScrollAmount(Component c, MouseWheelEvent me, JScrollBar scrollBar) { final int ratio = Registry.is("ide.smart.horizontal.scrolling") ? Math.max((int)Math.pow(c.getWidth() / scrollBar.getWidth() , 2), 10) : 10; // do annoying scrolling faster if smart scrolling is on diff --git a/platform/platform-resources-en/src/tips/AnnotationsAndDiffs.html b/platform/platform-resources-en/src/tips/AnnotationsAndDiffs.html new file mode 100644 index 000000000000..880371ab101b --- /dev/null +++ b/platform/platform-resources-en/src/tips/AnnotationsAndDiffs.html @@ -0,0 +1,17 @@ + + + + + + + +
+Version control annotations shows latest change of each line in one click!
+Use right-click on annotation and + Show Diff popup, +in context menu.

+

+
+ + \ No newline at end of file diff --git a/platform/platform-resources-en/src/tips/images/annotationShowDiff.png b/platform/platform-resources-en/src/tips/images/annotationShowDiff.png new file mode 100644 index 000000000000..d54ecdd70f38 Binary files /dev/null and b/platform/platform-resources-en/src/tips/images/annotationShowDiff.png differ diff --git a/platform/usageView/src/com/intellij/usages/ChunkExtractor.java b/platform/usageView/src/com/intellij/usages/ChunkExtractor.java index 6aa2f991828f..a7de001cb4e9 100644 --- a/platform/usageView/src/com/intellij/usages/ChunkExtractor.java +++ b/platform/usageView/src/com/intellij/usages/ChunkExtractor.java @@ -15,6 +15,7 @@ */ package com.intellij.usages; +import com.intellij.injected.editor.DocumentWindow; import com.intellij.lexer.Lexer; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; @@ -26,6 +27,7 @@ import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.fileTypes.PlainSyntaxHighlighter; import com.intellij.openapi.fileTypes.SyntaxHighlighter; +import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Segment; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiDocumentManager; @@ -101,10 +103,11 @@ public class ChunkExtractor { private ChunkExtractor(@NotNull PsiFile file) { myColorsScheme = UsageTreeColorsScheme.getInstance().getScheme(); - myDocument = PsiDocumentManager.getInstance(file.getProject()).getDocument(file); + Project project = file.getProject(); + myDocument = PsiDocumentManager.getInstance(project).getDocument(file); LOG.assertTrue(myDocument != null); final FileType fileType = file.getFileType(); - final SyntaxHighlighter highlighter = SyntaxHighlighter.PROVIDER.create(fileType, file.getProject(), file.getVirtualFile()); + final SyntaxHighlighter highlighter = SyntaxHighlighter.PROVIDER.create(fileType, project, file.getVirtualFile()); myHighlighter = highlighter == null ? new PlainSyntaxHighlighter() : highlighter; myLexer = myHighlighter.getHighlightingLexer(); myLexer.start(myDocument.getCharsSequence()); @@ -125,8 +128,15 @@ public class ChunkExtractor { int absoluteStartOffset = usageInfo2UsageAdapter.getNavigationOffset(); if (absoluteStartOffset == -1) return TextChunk.EMPTY_ARRAY; - final int lineNumber = myDocument.getLineNumber(absoluteStartOffset); - final int columnNumber = absoluteStartOffset - myDocument.getLineStartOffset(lineNumber); + Document visibleDocument = myDocument instanceof DocumentWindow ? ((DocumentWindow)myDocument).getDelegate() : myDocument; + int visibleStartOffset = myDocument instanceof DocumentWindow ? ((DocumentWindow)myDocument).injectedToHost(absoluteStartOffset) : absoluteStartOffset; + + int lineNumber = myDocument.getLineNumber(absoluteStartOffset); + //int columnNumber = absoluteStartOffset - myDocument.getLineStartOffset(lineNumber); + int visibleLineNumber = visibleDocument.getLineNumber(visibleStartOffset); + int visibleColumnNumber = visibleStartOffset - visibleDocument.getLineStartOffset(visibleLineNumber); + final List result = new ArrayList(); + appendPrefix(result, visibleLineNumber, visibleColumnNumber); int lineStartOffset = myDocument.getLineStartOffset(lineNumber); int lineEndOffset = lineStartOffset < myDocument.getTextLength() ? myDocument.getLineEndOffset(lineNumber) : 0; @@ -136,8 +146,6 @@ public class ChunkExtractor { if (myLexer.getTokenStart() > absoluteStartOffset) { myLexer.start(chars); } - final List result = new ArrayList(); - appendPrefix(result, lineNumber, columnNumber); if (lineEndOffset - lineStartOffset > MAX_LINE_TO_SHOW) { lineStartOffset = Math.max(lineStartOffset, absoluteStartOffset - OFFSET_BEFORE_TO_SHOW_WHEN_LONG_LINE); lineEndOffset = Math.min(lineEndOffset, absoluteStartOffset + OFFSET_AFTER_TO_SHOW_WHEN_LONG_LINE); diff --git a/platform/usageView/src/com/intellij/usages/UsageDataUtil.java b/platform/usageView/src/com/intellij/usages/UsageDataUtil.java index 134bee458bae..40fac535ca8a 100644 --- a/platform/usageView/src/com/intellij/usages/UsageDataUtil.java +++ b/platform/usageView/src/com/intellij/usages/UsageDataUtil.java @@ -41,7 +41,7 @@ public class UsageDataUtil { for (Usage usage : usages) { if (usage instanceof UsageInFile) { VirtualFile file = ((UsageInFile)usage).getFile(); - if (file.isValid()) { + if (file != null && file.isValid()) { result.add(file); } } diff --git a/platform/usageView/src/com/intellij/usages/UsageInfo2UsageAdapter.java b/platform/usageView/src/com/intellij/usages/UsageInfo2UsageAdapter.java index 1849cd9c696f..27043a47e744 100644 --- a/platform/usageView/src/com/intellij/usages/UsageInfo2UsageAdapter.java +++ b/platform/usageView/src/com/intellij/usages/UsageInfo2UsageAdapter.java @@ -16,6 +16,7 @@ package com.intellij.usages; import com.intellij.ide.SelectInEditorManager; +import com.intellij.injected.editor.VirtualFileWindow; import com.intellij.openapi.actionSystem.DataKey; import com.intellij.openapi.actionSystem.DataSink; import com.intellij.openapi.actionSystem.TypeSafeDataProvider; @@ -79,7 +80,7 @@ public class UsageInfo2UsageAdapter implements UsageInModule, @Override public void run() { PsiElement element = getElement(); - Document document = getDocument(); + Document document = PsiDocumentManager.getInstance(getProject()).getDocument(element.getContainingFile()); int startOffset = myUsageInfo.getNavigationOffset(); if (document != null) { @@ -197,7 +198,8 @@ public class UsageInfo2UsageAdapter implements UsageInModule, } public boolean canNavigate() { - return getFile().isValid(); + VirtualFile file = getFile(); + return file != null && file.isValid(); } public boolean canNavigateToSource() { @@ -218,7 +220,7 @@ public class UsageInfo2UsageAdapter implements UsageInModule, int offset = getUsageInfo().getNavigationOffset(); if (offset == -1) offset = myOffset; if (offset >= document.getTextLength()) { - int line = Math.max(0, Math.min(myLineNumber, document.getLineCount())); + int line = Math.max(0, Math.min(myLineNumber, document.getLineCount() - 1)); offset = document.getLineStartOffset(line); } return offset; @@ -323,16 +325,27 @@ public class UsageInfo2UsageAdapter implements UsageInModule, return myUsageInfo; } + // by start offset public int compareTo(final UsageInfo2UsageAdapter o) { VirtualFile containingFile = getFile(); + int shift1 = 0; + if (containingFile instanceof VirtualFileWindow) { + shift1 = ((VirtualFileWindow)containingFile).getDocumentWindow().injectedToHost(0); + containingFile = ((VirtualFileWindow)containingFile).getDelegate(); + } VirtualFile oContainingFile = o.getFile(); + int shift2 = 0; + if (oContainingFile instanceof VirtualFileWindow) { + shift2 = ((VirtualFileWindow)oContainingFile).getDocumentWindow().injectedToHost(0); + oContainingFile = ((VirtualFileWindow)oContainingFile).getDelegate(); + } if (containingFile == null && oContainingFile == null || !Comparing.equal(containingFile, oContainingFile)) { return 0; } Segment s1 = getFirstSegment(); Segment s2 = o.getFirstSegment(); if (s1 == null || s2 == null) return 0; - return s1.getStartOffset() - s2.getStartOffset(); + return s1.getStartOffset() + shift1 - s2.getStartOffset() - shift2; } public void rename(String newName) throws IncorrectOperationException { diff --git a/platform/usageView/src/com/intellij/usages/impl/UsageViewImpl.java b/platform/usageView/src/com/intellij/usages/impl/UsageViewImpl.java index 279f6321bedf..86c51f32237f 100644 --- a/platform/usageView/src/com/intellij/usages/impl/UsageViewImpl.java +++ b/platform/usageView/src/com/intellij/usages/impl/UsageViewImpl.java @@ -909,7 +909,8 @@ public class UsageViewImpl implements UsageView, UsageModelTracker.UsageModelTra for (Usage usage : usages) { if (usage instanceof UsageInFile) { UsageInFile usageInFile = (UsageInFile)usage; - result.add(usageInFile.getFile()); + VirtualFile file = usageInFile.getFile(); + if (file != null) result.add(file); } if (usage instanceof UsageInFiles) { diff --git a/platform/util/src/com/intellij/util/containers/ContainerUtil.java b/platform/util/src/com/intellij/util/containers/ContainerUtil.java index 531680d45241..008f26c62015 100644 --- a/platform/util/src/com/intellij/util/containers/ContainerUtil.java +++ b/platform/util/src/com/intellij/util/containers/ContainerUtil.java @@ -889,7 +889,7 @@ public class ContainerUtil { return value; } - public static V getOrCreate(final Map result, final T key, final Factory factory) { + public static V getOrCreate(@NotNull Map result, final T key, @NotNull Factory factory) { V value = result.get(key); if (value == null) { result.put(key, value = factory.create()); diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/VcsConfiguration.java b/platform/vcs-api/src/com/intellij/openapi/vcs/VcsConfiguration.java index feb81aec45b1..fc23a789e4b6 100644 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/VcsConfiguration.java +++ b/platform/vcs-api/src/com/intellij/openapi/vcs/VcsConfiguration.java @@ -28,6 +28,8 @@ import com.intellij.openapi.startup.StartupManager; import com.intellij.openapi.util.DefaultJDOMExternalizer; import com.intellij.openapi.util.InvalidDataException; import com.intellij.openapi.util.WriteExternalException; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; import org.jdom.Element; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -54,6 +56,10 @@ public final class VcsConfiguration implements PersistentStateComponent @NonNls private static final String VALUE_ATTR = "value"; @NonNls private static final String CONFIRM_MOVE_TO_FAILED_COMMIT_ELEMENT = "confirmMoveToFailedCommit"; @NonNls private static final String CONFIRM_REMOVE_EMPTY_CHANGELIST_ELEMENT = "confirmRemoveEmptyChangelist"; + + @NonNls public static final String PATCH = "patch"; + @NonNls public static final String DIFF = "diff"; + private Project myProject; public boolean OFFER_MOVE_TO_ANOTHER_CHANGELIST_ON_PARTIAL_COMMIT = true; @@ -73,6 +79,7 @@ public final class VcsConfiguration implements PersistentStateComponent public int CHANGED_ON_SERVER_INTERVAL = 60; public boolean SHOW_ONLY_CHANGED_IN_SELECTION_DIFF = true; public boolean CHECK_COMMIT_MESSAGE_SPELLING = true; + public String DEFAULT_PATCH_EXTENSION = PATCH; public enum StandardOption { ADD(VcsBundle.message("vcs.command.name.add")), @@ -346,4 +353,17 @@ public final class VcsConfiguration implements PersistentStateComponent } + public String getPatchFileExtension() { + return DEFAULT_PATCH_EXTENSION; + } + + public void acceptLastCreatedPatchName(final String string) { + if (StringUtil.isEmptyOrSpaces(string)) return; + final String extension = FileUtil.getExtension(string); + if (DIFF.equalsIgnoreCase(extension)) { + DEFAULT_PATCH_EXTENSION = DIFF; + } else if (PATCH.equalsIgnoreCase(extension)) { + DEFAULT_PATCH_EXTENSION = PATCH; + } + } } diff --git a/platform/vcs-api/src/com/intellij/util/continuation/ContinuationContext.java b/platform/vcs-api/src/com/intellij/util/continuation/ContinuationContext.java index 017f841458eb..6f70beb74d03 100644 --- a/platform/vcs-api/src/com/intellij/util/continuation/ContinuationContext.java +++ b/platform/vcs-api/src/com/intellij/util/continuation/ContinuationContext.java @@ -47,78 +47,6 @@ public interface ContinuationContext { void suspend(); void ping(); - class GatheringContinuationContext implements ContinuationContext { - private final List myList; - - public GatheringContinuationContext() { - myList = new ArrayList(); - } - - public List getList() { - return myList; - } - - @Override - public void cancelEverything() { - } - - @Override - public void addExceptionHandler(Class clazz, Consumer consumer) { - } - - @Override - public boolean handleException(Exception e) { - return false; - } - - @Override - public void throwDisaster(Object disaster, final Object cure) { - } - - @Override - public void keepExisting(Object disaster, Object cure) { - } - - @Override - public void next(TaskDescriptor... next) { - myList.addAll(0, Arrays.asList(next)); - } - - @Override - public void next(List next) { - myList.addAll(0, next); - } - - @Override - public void last(TaskDescriptor... next) { - myList.addAll(Arrays.asList(next)); - } - - @Override - public void last(List next) { - myList.addAll(next); - } - - @Override - public void after(@NotNull TaskDescriptor inQueue, TaskDescriptor... next) { - int idx = -1; - for (int i = 0; i < myList.size(); i++) { - final TaskDescriptor descriptor = myList.get(i); - if (inQueue == descriptor) { - idx = i; - break; - } - } - assert idx != -1; - myList.addAll(idx, Arrays.asList(next)); - } - - @Override - public void suspend() { - } - - @Override - public void ping() { - } - } -} + void removeNewTasksPatcher(@NotNull final Consumer consumer); + void addNewTasksPatcher(@NotNull final Consumer consumer); +} \ No newline at end of file diff --git a/platform/vcs-api/src/com/intellij/util/continuation/ContinuationFinalTasksInserter.java b/platform/vcs-api/src/com/intellij/util/continuation/ContinuationFinalTasksInserter.java new file mode 100644 index 000000000000..51751319ff47 --- /dev/null +++ b/platform/vcs-api/src/com/intellij/util/continuation/ContinuationFinalTasksInserter.java @@ -0,0 +1,46 @@ +/* + * Copyright 2000-2011 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 com.intellij.util.continuation; + +import com.intellij.util.Consumer; + +/** + * @author irengrig + * Date: 4/7/11 + * Time: 7:25 PM + */ +public class ContinuationFinalTasksInserter { + private final ContinuationContext myContext; + private final Consumer myPatcher; + + public ContinuationFinalTasksInserter(ContinuationContext context) { + myContext = context; + myPatcher = new Consumer() { + @Override + public void consume(TaskDescriptor taskDescriptor) { + taskDescriptor.setHaveMagicCure(true); + } + }; + } + + public void allNextAreFinal() { + myContext.addNewTasksPatcher(myPatcher); + } + + public void removeFinalPropertyAdder() { + myContext.removeNewTasksPatcher(myPatcher); + } +} diff --git a/platform/vcs-api/src/com/intellij/util/continuation/GatheringContinuationContext.java b/platform/vcs-api/src/com/intellij/util/continuation/GatheringContinuationContext.java new file mode 100644 index 000000000000..dbfb12891517 --- /dev/null +++ b/platform/vcs-api/src/com/intellij/util/continuation/GatheringContinuationContext.java @@ -0,0 +1,111 @@ +/* + * Copyright 2000-2011 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 com.intellij.util.continuation; + +import com.intellij.util.Consumer; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** +* @author irengrig +* Date: 4/7/11 +* Time: 6:43 PM +*/ +public class GatheringContinuationContext implements ContinuationContext { + private final List myList; + + public GatheringContinuationContext() { + myList = new ArrayList(); + } + + public List getList() { + return myList; + } + + @Override + public void cancelEverything() { + } + + @Override + public void addExceptionHandler(Class clazz, Consumer consumer) { + } + + @Override + public boolean handleException(Exception e) { + return false; + } + + @Override + public void throwDisaster(Object disaster, final Object cure) { + } + + @Override + public void keepExisting(Object disaster, Object cure) { + } + + @Override + public void next(TaskDescriptor... next) { + myList.addAll(0, Arrays.asList(next)); + } + + @Override + public void next(List next) { + myList.addAll(0, next); + } + + @Override + public void last(TaskDescriptor... next) { + myList.addAll(Arrays.asList(next)); + } + + @Override + public void last(List next) { + myList.addAll(next); + } + + @Override + public void after(@NotNull TaskDescriptor inQueue, TaskDescriptor... next) { + int idx = -1; + for (int i = 0; i < myList.size(); i++) { + final TaskDescriptor descriptor = myList.get(i); + if (inQueue == descriptor) { + idx = i; + break; + } + } + assert idx != -1; + myList.addAll(idx, Arrays.asList(next)); + } + + @Override + public void suspend() { + } + + @Override + public void ping() { + } + + @Override + public void addNewTasksPatcher(@NotNull Consumer consumer) { + } + + @Override + public void removeNewTasksPatcher(@NotNull Consumer consumer) { + } +} diff --git a/platform/vcs-api/src/com/intellij/util/continuation/TaskDescriptor.java b/platform/vcs-api/src/com/intellij/util/continuation/TaskDescriptor.java index 43cf5f149afd..d2ab24bc4749 100644 --- a/platform/vcs-api/src/com/intellij/util/continuation/TaskDescriptor.java +++ b/platform/vcs-api/src/com/intellij/util/continuation/TaskDescriptor.java @@ -22,6 +22,7 @@ import java.util.HashMap; import java.util.Map; public abstract class TaskDescriptor { + // this also means that it would be called in case of chain cancel() private boolean myHaveMagicCure; private final String myName; @NotNull diff --git a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/formove/PatchApplier.java b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/formove/PatchApplier.java index f39ba0bd1da1..9355133959f8 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/formove/PatchApplier.java +++ b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/formove/PatchApplier.java @@ -47,10 +47,7 @@ import com.intellij.openapi.vfs.newvfs.RefreshSession; import com.intellij.openapi.wm.ToolWindowManager; import com.intellij.util.Consumer; import com.intellij.util.WaitForProgressToShow; -import com.intellij.util.continuation.Continuation; -import com.intellij.util.continuation.ContinuationContext; -import com.intellij.util.continuation.TaskDescriptor; -import com.intellij.util.continuation.Where; +import com.intellij.util.continuation.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -111,9 +108,9 @@ public class PatchApplier { @AsynchronousExecution public void execute(boolean showSuccessNotification) { - final Continuation continuation = new Continuation(myProject, true); - final ContinuationContext.GatheringContinuationContext initContext = - new ContinuationContext.GatheringContinuationContext(); + final Continuation continuation = Continuation.createForCurrentProgress(myProject, true, "Apply patch"); + final GatheringContinuationContext initContext = + new GatheringContinuationContext(); scheduleSelf(showSuccessNotification, initContext); continuation.run(initContext.getList()); } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/actions/AbstractShowDiffAction.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/actions/AbstractShowDiffAction.java index 1f1ba9523d02..392f1112fdac 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/actions/AbstractShowDiffAction.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/actions/AbstractShowDiffAction.java @@ -17,6 +17,7 @@ package com.intellij.openapi.vcs.actions; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.editor.Document; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.vcs.AbstractVcs; @@ -89,7 +90,10 @@ public abstract class AbstractShowDiffAction extends AbstractVcsAction{ final Project project = vcsContext.getProject(); final VirtualFile selectedFile = vcsContext.getSelectedFiles()[0]; final FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance(); - fileDocumentManager.saveDocument(fileDocumentManager.getDocument(selectedFile)); + final Document document = fileDocumentManager.getDocument(selectedFile); + if (document != null) { + fileDocumentManager.saveDocument(document); + } final ProjectLevelVcsManager vcsManager = ProjectLevelVcsManager.getInstance(project); final AbstractVcs vcs = vcsManager.getVcsFor(selectedFile); diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/CreatePatchCommitExecutor.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/CreatePatchCommitExecutor.java index e6d35a44e239..8557825a87bf 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/CreatePatchCommitExecutor.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/CreatePatchCommitExecutor.java @@ -29,6 +29,7 @@ import com.intellij.openapi.util.InvalidDataException; import com.intellij.openapi.util.JDOMExternalizable; import com.intellij.openapi.util.WriteExternalException; import com.intellij.openapi.vcs.VcsBundle; +import com.intellij.openapi.vcs.VcsConfiguration; import com.intellij.openapi.vcs.changes.*; import com.intellij.openapi.vcs.changes.shelf.ShelveChangesManager; import com.intellij.util.WaitForProgressToShow; @@ -118,7 +119,7 @@ public class CreatePatchCommitExecutor implements CommitExecutorWithHelp, Projec if (PATCH_PATH.length() == 0) { PATCH_PATH = myProject.getBaseDir().getPresentableUrl(); } - myPanel.setFileName(ShelveChangesManager.suggestPatchName(commitMessage, new File(PATCH_PATH))); + myPanel.setFileName(ShelveChangesManager.suggestPatchName(myProject, commitMessage, new File(PATCH_PATH), null)); myPanel.setReversePatch(REVERSE_PATCH); return myPanel.getPanel(); } @@ -150,6 +151,7 @@ public class CreatePatchCommitExecutor implements CommitExecutorWithHelp, Projec try { final String fileName = myPanel.getFileName(); final File file = new File(fileName).getAbsoluteFile(); + VcsConfiguration.getInstance(myProject).acceptLastCreatedPatchName(file.getName()); PATCH_PATH = file.getParent(); REVERSE_PATCH = myPanel.isReversePatch(); diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/CreatePatchConfigurationPanel.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/CreatePatchConfigurationPanel.java index b1800bffdd4d..3a538b536c03 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/CreatePatchConfigurationPanel.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/CreatePatchConfigurationPanel.java @@ -141,7 +141,7 @@ public class CreatePatchConfigurationPanel { } public boolean isOkToExecute() { - return myErrorLabel.getText() == null || myErrorLabel.getText().length() == 0; + return myErrorLabel.getText() == null || myErrorLabel.getText().length() == 0; } public String getError() { diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/PatchFileType.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/PatchFileType.java index c46667b27531..eeb3edf85b69 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/PatchFileType.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/PatchFileType.java @@ -36,11 +36,12 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; public class PatchFileType implements FileType { + public static final String NAME = "PATCH"; @NotNull @NonNls public String getName() { - return "PATCH"; + return NAME; } @NotNull diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/PatchNameChecker.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/PatchNameChecker.java index 61ab41ab393c..a4263be19c96 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/PatchNameChecker.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/PatchNameChecker.java @@ -21,8 +21,10 @@ public class PatchNameChecker { public final static int MAX = 100; private final String myName; private String myError; + private final String myPath; public PatchNameChecker(final String name) { + myPath = name; myName = new File(name).getName(); } @@ -33,6 +35,9 @@ public class PatchNameChecker { } else if (myName.length() > MAX) { myError = "File name length cannot exceed " + MAX + " characters"; return false; + } else if (new File(myPath).exists()) { + myError = "File with the same name already exists"; + return false; } return true; } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/shelf/ImportIntoShelfAction.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/shelf/ImportIntoShelfAction.java index 8b185f96e520..a397f535c2bc 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/shelf/ImportIntoShelfAction.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/shelf/ImportIntoShelfAction.java @@ -24,7 +24,9 @@ import com.intellij.openapi.fileChooser.FileChooserFactory; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.MessageType; +import com.intellij.openapi.ui.Messages; import com.intellij.openapi.vcs.AbstractVcsHelper; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier; @@ -60,28 +62,49 @@ public class ImportIntoShelfAction extends DumbAwareAction { @Override public void consume(final VirtualFile[] virtualFiles) { if (virtualFiles.length == 0) return; - ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() { - @Override - public void run() { - final List exceptions = new ArrayList(); - final List lists = - ShelveChangesManager.getInstance(project).importChangeLists(Arrays.asList(virtualFiles), new Consumer() { - @Override - public void consume(VcsException e) { - exceptions.add(e); - } - }); - if (! lists.isEmpty()) { - ShelvedChangesViewManager.getInstance(project).activateView(lists.get(lists.size() - 1)); + //gatherPatchFiles + final ProgressManager pm = ProgressManager.getInstance(); + final ShelveChangesManager shelveChangesManager = ShelveChangesManager.getInstance(project); + final List asList = Arrays.asList(virtualFiles); + + final List patchTypeFiles = new ArrayList(); + final boolean filesFound = pm.runProcessWithProgressSynchronously(new Runnable() { + @Override + public void run() { + patchTypeFiles.addAll(shelveChangesManager.gatherPatchFiles(asList)); } - if (! exceptions.isEmpty()) { - AbstractVcsHelper.getInstance(project).showErrors(exceptions, "Import patches into shelf"); + }, "Looking for patch files...", true, project); + if (! filesFound || patchTypeFiles.isEmpty()) return; + if (! patchTypeFiles.equals(asList)) { + final int toImport = Messages.showYesNoDialog(project, "Found " + + (patchTypeFiles.size() == 1 + ? "one patch file (" + patchTypeFiles.get(0).getPath() + ")." + : (patchTypeFiles.size() + " patch files.")) + + "\nContinue with import?", "Import patches", Messages.getQuestionIcon()); + if (DialogWrapper.CANCEL_EXIT_CODE == toImport) return; + } + pm.runProcessWithProgressSynchronously(new Runnable() { + @Override + public void run() { + final List exceptions = new ArrayList(); + final List lists = + shelveChangesManager.importChangeLists(patchTypeFiles, new Consumer() { + @Override + public void consume(VcsException e) { + exceptions.add(e); + } + }); + if (!lists.isEmpty()) { + ShelvedChangesViewManager.getInstance(project).activateView(lists.get(lists.size() - 1)); + } + if (!exceptions.isEmpty()) { + AbstractVcsHelper.getInstance(project).showErrors(exceptions, "Import patches into shelf"); + } + if (lists.isEmpty() && exceptions.isEmpty()) { + VcsBalloonProblemNotifier.showOverChangesView(project, "No patches found", MessageType.WARNING); + } } - if (lists.isEmpty() && exceptions.isEmpty()) { - VcsBalloonProblemNotifier.showOverChangesView(project, "No patches found", MessageType.WARNING); - } - } - }, "Import patches into shelf", true, project); + }, "Import patches into shelf", true, project); } }); } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/shelf/ShelveChangesManager.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/shelf/ShelveChangesManager.java index 645b96430f24..97ec0edcd960 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/shelf/ShelveChangesManager.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/shelf/ShelveChangesManager.java @@ -37,21 +37,17 @@ import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.*; import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vcs.FilePath; -import com.intellij.openapi.vcs.FilePathImpl; -import com.intellij.openapi.vcs.VcsBundle; -import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.*; import com.intellij.openapi.vcs.changes.*; +import com.intellij.openapi.vcs.changes.patch.PatchFileType; import com.intellij.openapi.vcs.changes.patch.PatchNameChecker; import com.intellij.openapi.vcs.changes.ui.RollbackWorker; +import com.intellij.openapi.vcs.impl.VcsFileTypeFactory; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.Consumer; import com.intellij.util.PathUtil; import com.intellij.util.SmartList; -import com.intellij.util.continuation.Continuation; -import com.intellij.util.continuation.ContinuationContext; -import com.intellij.util.continuation.TaskDescriptor; -import com.intellij.util.continuation.Where; +import com.intellij.util.continuation.*; import com.intellij.util.messages.MessageBus; import com.intellij.util.messages.Topic; import com.intellij.util.text.CharArrayCharSequence; @@ -68,8 +64,6 @@ import java.util.*; public class ShelveChangesManager implements ProjectComponent, JDOMExternalizable { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.changes.shelf.ShelveChangesManager"); - @NonNls private static final String PATCH_EXTENSION = "patch"; - public static ShelveChangesManager getInstance(Project project) { return PeriodicalTasksCloser.getInstance().safeGetComponent(project, ShelveChangesManager.class); } @@ -221,17 +215,30 @@ public class ShelveChangesManager implements ProjectComponent, JDOMExternalizabl } } + public List gatherPatchFiles(final Collection files) { + final List result = new ArrayList(); + + final LinkedList filesQueue = new LinkedList(files); + while (! filesQueue.isEmpty()) { + ProgressManager.checkCanceled(); + final VirtualFile file = filesQueue.removeFirst(); + if (file.isDirectory()) { + filesQueue.addAll(Arrays.asList(file.getChildren())); + continue; + } + if (PatchFileType.NAME.equals(file.getFileType().getName())) { + result.add(file); + } + } + + return result; + } + public List importChangeLists(final Collection files, final Consumer exceptionConsumer) { final List result = new ArrayList(files.size()); try { - final LinkedList filesQueue = new LinkedList(files); - while (! filesQueue.isEmpty()) { + for (VirtualFile file : files) { ProgressManager.checkCanceled(); - final VirtualFile file = filesQueue.removeFirst(); - if (file.isDirectory()) { - filesQueue.addAll(Arrays.asList(file.getChildren())); - continue; - } final String description = file.getNameWithoutExtension().replace('_', ' '); final File patchPath = getPatchPath(description); @@ -289,16 +296,17 @@ public class ShelveChangesManager implements ProjectComponent, JDOMExternalizabl file.mkdirs(); } - return suggestPatchName(commitMessage.length() > PatchNameChecker.MAX ? (commitMessage.substring(0, PatchNameChecker.MAX)) : - commitMessage, file); + return suggestPatchName(myProject, commitMessage.length() > PatchNameChecker.MAX ? (commitMessage.substring(0, PatchNameChecker.MAX)) : + commitMessage, file, VcsConfiguration.PATCH); } - public static File suggestPatchName(final String commitMessage, final File file) { + public static File suggestPatchName(Project project, final String commitMessage, final File file, String extension) { @NonNls String defaultPath = PathUtil.suggestFileName(commitMessage); if (defaultPath.length() == 0) { defaultPath = "unnamed"; } - return FileUtil.findSequentNonexistentFile(file, defaultPath, PATCH_EXTENSION); + return FileUtil.findSequentNonexistentFile(file, defaultPath, + extension == null ? VcsConfiguration.getInstance(project).getPatchFileExtension() : extension); } public void unshelveChangeList(final ShelvedChangeList changeList, @Nullable final List changes, @@ -312,8 +320,8 @@ public class ShelveChangesManager implements ProjectComponent, JDOMExternalizabl @Nullable final List binaryFiles, @Nullable final LocalChangeList targetChangeList, boolean showSuccessNotification) { - final Continuation continuation = new Continuation(myProject, true); - final ContinuationContext.GatheringContinuationContext initContext = new ContinuationContext.GatheringContinuationContext(); + final Continuation continuation = Continuation.createForCurrentProgress(myProject, true, "Unshelve changes"); + final GatheringContinuationContext initContext = new GatheringContinuationContext(); scheduleUnshelveChangeList(changeList, changes, binaryFiles, targetChangeList, showSuccessNotification, initContext); continuation.run(initContext.getList()); } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/shelf/UnshelveChangesAction.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/shelf/UnshelveChangesAction.java index ba5d48422360..b83ae6e502d3 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/shelf/UnshelveChangesAction.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/shelf/UnshelveChangesAction.java @@ -27,11 +27,16 @@ import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.PlatformDataKeys; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; import com.intellij.openapi.vcs.VcsBundle; +import com.intellij.openapi.vcs.changes.BackgroundFromStartOption; import com.intellij.openapi.vcs.changes.ChangeListManager; import com.intellij.openapi.vcs.changes.LocalChangeList; import com.intellij.openapi.vcs.changes.ui.ChangeListChooser; +import org.jetbrains.annotations.NotNull; import java.util.List; @@ -65,7 +70,7 @@ public class UnshelveChangesAction extends AnAction { } } defaultName = changeLists.length == 1 ? changeLists [0].DESCRIPTION : null; - ChangeListChooser chooser = new ChangeListChooser(project, allChangeLists, list, + final ChangeListChooser chooser = new ChangeListChooser(project, allChangeLists, list, VcsBundle.message("unshelve.changelist.chooser.title"), defaultName); chooser.show(); if (!chooser.isOK()) { @@ -74,9 +79,16 @@ public class UnshelveChangesAction extends AnAction { FileDocumentManager.getInstance().saveAllDocuments(); - for(ShelvedChangeList changeList: changeLists) { - ShelveChangesManager.getInstance(project).unshelveChangeList(changeList, changes, binaryFiles, chooser.getSelectedList()); - } + final List finalBinaryFiles = binaryFiles; + final List finalChanges = changes; + ProgressManager.getInstance().run(new Task.Backgroundable(project, "Unshelve changes", true, BackgroundFromStartOption.getInstance()) { + @Override + public void run(@NotNull ProgressIndicator indicator) { + for(ShelvedChangeList changeList: changeLists) { + ShelveChangesManager.getInstance(project).unshelveChangeList(changeList, finalChanges, finalBinaryFiles, chooser.getSelectedList()); + } + } + }); } @Override diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/VcsFileTypeFactory.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/VcsFileTypeFactory.java index 5f1fc1676208..aeee91a2b280 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/VcsFileTypeFactory.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/VcsFileTypeFactory.java @@ -17,6 +17,7 @@ package com.intellij.openapi.vcs.impl; import com.intellij.openapi.fileTypes.FileTypeFactory; import com.intellij.openapi.fileTypes.FileTypeConsumer; +import com.intellij.openapi.vcs.VcsConfiguration; import com.intellij.openapi.vcs.changes.patch.PatchFileType; import org.jetbrains.annotations.NotNull; @@ -25,6 +26,6 @@ import org.jetbrains.annotations.NotNull; */ public class VcsFileTypeFactory extends FileTypeFactory { public void createFileTypes(@NotNull FileTypeConsumer consumer) { - consumer.consume(new PatchFileType(), "patch;diff"); + consumer.consume(new PatchFileType(), VcsConfiguration.PATCH + ";" + VcsConfiguration.DIFF); } } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/update/TestContinuationAction.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/update/TestContinuationAction.java new file mode 100644 index 000000000000..a78cc7ee93e2 --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/update/TestContinuationAction.java @@ -0,0 +1,160 @@ +/* + * Copyright 2000-2011 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 com.intellij.openapi.vcs.update; + +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.progress.PerformInBackgroundOption; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; +import com.intellij.util.Icons; +import com.intellij.util.continuation.*; +import org.jetbrains.annotations.NotNull; + +/** + * @author irengrig + * Date: 4/7/11 + * Time: 7:06 PM + */ +public class TestContinuationAction extends AnAction { + public TestContinuationAction() { + super("Test Continuation", "Test Continuation", Icons.ADVICE_ICON); + } + + @Override + public void actionPerformed(final AnActionEvent e) { + final Project project = PlatformDataKeys.PROJECT.getData(e.getDataContext()); + if (project == null) return; + + ProgressManager.getInstance().run(new Task.Backgroundable(project, "Test Continuation", true, + new PerformInBackgroundOption() { + @Override + public boolean shouldStartInBackground() { + return false; + } + + @Override + public void processSentToBackground() { + } + }) { + @Override + public void run(@NotNull ProgressIndicator indicator) { + final Continuation continuation = Continuation.createForCurrentProgress(project, true, e.getPresentation().getText()); + final ReportTask finalBlock = new ReportTask("I'm finally block!"); + finalBlock.setHaveMagicCure(true); + continuation.run(new TaskDescriptor[] {new LongTaskDescriptor("First"), new ReportTask("First complete"), + new TaskDescriptor("Adding task", Where.POOLED) { + @Override + public void run(final ContinuationContext context) { + addMore(context); + try { + Thread.sleep(10000); + } + catch (InterruptedException e1) { + // + } + } + }, + new LongTaskDescriptor("Second"), new ReportTask("Second complete"), + new TaskDescriptor("Adding task 2", Where.POOLED) { + @Override + public void run(final ContinuationContext context) { + addMoreSurviving(context); + try { + Thread.sleep(10000); + } + catch (InterruptedException e1) { + // + } + throw new IllegalStateException(); + /*context.suspend(); + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + context.ping(); + } + }, ModalityState.NON_MODAL);*/ + } + }, + new LongTaskDescriptor("Third"), new ReportTask("Third complete"), + finalBlock}); + } + + @Override + public void onCancel() { + Messages.showInfoMessage("cancel!", myTitle); + } + + @Override + public void onSuccess() { + Messages.showInfoMessage("success!", myTitle); + } + }); + } + + private void addMore(ContinuationContext context) { + context.next(new LongTaskDescriptor("Inside killable"), new ReportTask("Inside killable complete")); + } + + private void addMoreSurviving(ContinuationContext context) { + final ContinuationFinalTasksInserter finalTasksInserter = new ContinuationFinalTasksInserter(context); + finalTasksInserter.allNextAreFinal(); + context.next(new LongTaskDescriptor("Inside surviving"), new ReportTask("Inside surviving complete")); + finalTasksInserter.removeFinalPropertyAdder(); + } + + @Override + public void update(AnActionEvent e) { + final Project project = PlatformDataKeys.PROJECT.getData(e.getDataContext()); + e.getPresentation().setEnabled(project != null); + } + + private static class ReportTask extends TaskDescriptor { + private ReportTask(String name) { + super(name, Where.AWT); + } + + @Override + public void run(ContinuationContext context) { + Messages.showInfoMessage(getName(), "Result"); + } + } + + private static class LongTaskDescriptor extends TaskDescriptor { + private LongTaskDescriptor(final String name) { + super(name, Where.POOLED); + } + + @Override + public void run(ContinuationContext context) { + final ProgressIndicator pi = ProgressManager.getInstance().getProgressIndicator(); + pi.setText(getName()); + try { + Thread.sleep(10000); + } + catch (InterruptedException e) { + // + } + pi.setText(""); + } + } +} diff --git a/platform/vcs-impl/src/com/intellij/util/continuation/Continuation.java b/platform/vcs-impl/src/com/intellij/util/continuation/Continuation.java index 94c309a4dd6d..33f9074d3c36 100644 --- a/platform/vcs-impl/src/com/intellij/util/continuation/Continuation.java +++ b/platform/vcs-impl/src/com/intellij/util/continuation/Continuation.java @@ -15,91 +15,50 @@ */ package com.intellij.util.continuation; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.progress.EmptyProgressIndicator; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.Task; -import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator; -import com.intellij.openapi.progress.impl.ProgressManagerImpl; import com.intellij.openapi.project.Project; -import com.intellij.openapi.vcs.CalledInAny; -import com.intellij.openapi.vcs.CalledInAwt; -import com.intellij.openapi.vcs.changes.BackgroundFromStartOption; import com.intellij.util.Consumer; -import com.intellij.util.Processor; -import com.intellij.util.WaitForProgressToShow; import com.intellij.util.concurrency.Semaphore; -import com.sun.tools.javac.code.Attribute; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.*; public class Continuation { private GeneralRunner myGeneralRunner; - public Continuation(final Project project, final boolean cancellable) { - myGeneralRunner = new GeneralRunner(project, cancellable); + private Continuation(GeneralRunner generalRunner) { + myGeneralRunner = generalRunner; + } + + public static Continuation createForCurrentProgress(final Project project, final boolean cancellable, final String commonTitle) { + return new Continuation(new SameProgressRunner(project, cancellable, commonTitle)); + } + + public static Continuation createFragmented(final Project project, final boolean cancellable) { + return new Continuation(new SeparatePiecesRunner(project, cancellable)); } public void run(final TaskDescriptor... tasks) { if (tasks.length == 0) return; myGeneralRunner.next(tasks); - pingRunnerInCorrectThread(); + myGeneralRunner.ping(); } public void run(final List tasks) { if (tasks.isEmpty()) return; myGeneralRunner.next(tasks); - pingRunnerInCorrectThread(); - } - - public void runAndWait(final TaskDescriptor... tasks) { - runAndWait(Arrays.asList(tasks)); - } - - public void runAndWait(final List tasks) { - final Semaphore semaphore = new Semaphore(); - semaphore.down(); - tasks.add(new TaskDescriptor("", Where.AWT) { - @Override - public void run(ContinuationContext context) { - semaphore.up(); - } - }); - run(tasks); - semaphore.waitFor(); + myGeneralRunner.ping(); } public void addExceptionHandler(final Class clazz, final Consumer consumer) { myGeneralRunner.addExceptionHandler(clazz, consumer); } - public void runIndirect(final Consumer consumer) { - consumer.consume(myGeneralRunner); - if (myGeneralRunner.isEmpty()) return; - - pingRunnerInCorrectThread(); - } - public void resume() { myGeneralRunner.ping(); } - private void pingRunnerInCorrectThread() { - if (! ApplicationManager.getApplication().isDispatchThread()) { - WaitForProgressToShow.runOrInvokeLaterAboveProgress(new Runnable() { - public void run() { - myGeneralRunner.ping(); - } - }, null, myGeneralRunner.getProject()); - } else { - myGeneralRunner.ping(); - } - } - public void clearQueue() { myGeneralRunner.cancelEverything(); } @@ -116,224 +75,7 @@ public class Continuation { return myGeneralRunner.isEmpty(); } - private static class TaskWrapper extends Task.Backgroundable { - private final TaskDescriptor myTaskDescriptor; - private final GeneralRunner myGeneralRunner; - - private TaskWrapper(@Nullable Project project, - @NotNull String title, - boolean canBeCancelled, - TaskDescriptor taskDescriptor, - GeneralRunner generalRunner) { - super(project, title, canBeCancelled, BackgroundFromStartOption.getInstance()); - myTaskDescriptor = taskDescriptor; - myGeneralRunner = generalRunner; - } - - @Override - public void run(@NotNull ProgressIndicator indicator) { - myTaskDescriptor.run(myGeneralRunner); - } - - @Override - public void onSuccess() { - myGeneralRunner.ping(); - } - } - - private static class GeneralRunner implements ContinuationContext { - private final Project myProject; - private final boolean myCancellable; - private final List myQueue; - private final Object myQueueLock; - private volatile boolean myTriggerSuspend; - private ProgressIndicator myIndicator; - private final Map myDisasters; - private final Map, Consumer> myHandlersMap; - - private GeneralRunner(final Project project, boolean cancellable) { - myProject = project; - myCancellable = cancellable; - myQueueLock = new Object(); - myQueue = new LinkedList(); - myDisasters = new HashMap(); - myHandlersMap = new HashMap, Consumer>(); - } - - public void addExceptionHandler(final Class clazz, final Consumer consumer) { - synchronized (myQueueLock) { - myHandlersMap.put(clazz, new Consumer() { - @Override - public void consume(Exception e) { - if (! clazz.isAssignableFrom(e.getClass())) { - throw new RuntimeException(e); - } - consumer.consume((T) e); - } - }); - } - } - - public Project getProject() { - return myProject; - } - - public void clearDisasters() { - synchronized (myQueueLock) { - myDisasters.clear(); - } - } - - @Override - public boolean handleException(Exception e) { - synchronized (myQueueLock) { - final Class aClass = e.getClass(); - Consumer consumer = myHandlersMap.get(e.getClass()); - if (consumer != null) { - consumer.consume(e); - return true; - } - for (Map.Entry, Consumer> entry : myHandlersMap.entrySet()) { - if (entry.getKey().isAssignableFrom(aClass)) { - entry.getValue().consume(e); - return true; - } - } - } - return false; - } - - @CalledInAny - public void cancelEverything() { - synchronized (myQueueLock) { - myQueue.clear(); - } - } - - public void cancelCurrent() { - if (myIndicator != null) { - myIndicator.cancel(); - } - } - - public void suspend() { - myTriggerSuspend = true; - } - - @Override - public void keepExisting(Object disaster, Object cure) { - synchronized (myQueueLock) { - for (TaskDescriptor taskDescriptor : myQueue) { - taskDescriptor.addCure(disaster, cure); - } - } - } - - @Override - public void throwDisaster(@NotNull Object disaster, @NotNull final Object cure) { - synchronized (myQueueLock) { - final Iterator iterator = myQueue.iterator(); - while (iterator.hasNext()) { - final TaskDescriptor taskDescriptor = iterator.next(); - if (taskDescriptor.isHaveMagicCure()) continue; - final Object taskCure = taskDescriptor.hasCure(disaster); - if (! cure.equals(taskCure)) { - iterator.remove(); - } - } - myDisasters.put(disaster, cure); - } - } - - @Override - public void after(@NotNull TaskDescriptor inQueue, TaskDescriptor... next) { - synchronized (myQueueLock) { - int idx = -1; - int i = 0; - for (TaskDescriptor descriptor : myQueue) { - if (descriptor == inQueue) { - idx = i; - break; - } - ++ i; - } - assert idx != -1; - myQueue.addAll(idx + 1, Arrays.asList(next)); - } - } - - @CalledInAny - public void next(TaskDescriptor... next) { - synchronized (myQueueLock) { - myQueue.addAll(0, Arrays.asList(next)); - } - } - - public void next(List next) { - synchronized (myQueueLock) { - myQueue.addAll(0, next); - } - } - - @Override - public void last(List next) { - synchronized (myQueueLock) { - myQueue.addAll(next); - } - } - - @Override - public void last(TaskDescriptor... next) { - synchronized (myQueueLock) { - myQueue.addAll(Arrays.asList(next)); - } - } - - public boolean isEmpty() { - synchronized (myQueueLock) { - return myQueue.isEmpty(); - } - } - - @CalledInAwt - public void ping() { - ApplicationManager.getApplication().assertIsDispatchThread(); - - while (true) { - // stop if project is being disposed - if (! myProject.isOpen()) return; - - TaskDescriptor current; - synchronized (myQueueLock) { - if (myQueue.isEmpty()) return; - if (myTriggerSuspend) { - myTriggerSuspend = false; - return; - } - current = myQueue.remove(0); - // check if some tasks were scheduled after disaster was thrown, anyway, they should also be checked for cure - if (! current.isHaveMagicCure()) { - for (Map.Entry entry : myDisasters.entrySet()) { - if (! entry.getValue().equals(current.hasCure(entry.getKey()))) { - current = null; - break; - } - } - } - if (current == null) continue; - } - - if (Where.AWT.equals(current.getWhere())) { - myIndicator = null; - current.run(this); - } else { - final TaskWrapper task = new TaskWrapper(myProject, current.getName(), myCancellable, current, this); - myIndicator = ApplicationManager.getApplication().isUnitTestMode() ? new EmptyProgressIndicator() : - new BackgroundableProcessIndicator(task); - ProgressManagerImpl.runProcessWithProgressAsynchronously(task, myIndicator); - return; - } - } - } + public void onCancel() { + myGeneralRunner.onCancel(); } } diff --git a/platform/vcs-impl/src/com/intellij/util/continuation/GeneralRunner.java b/platform/vcs-impl/src/com/intellij/util/continuation/GeneralRunner.java new file mode 100644 index 000000000000..04c3376e8df8 --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/util/continuation/GeneralRunner.java @@ -0,0 +1,272 @@ +/* + * Copyright 2000-2011 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 com.intellij.util.continuation; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.progress.EmptyProgressIndicator; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator; +import com.intellij.openapi.progress.impl.ProgressManagerImpl; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.CalledInAny; +import com.intellij.openapi.vcs.CalledInAwt; +import com.intellij.util.Consumer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +/** +* @author irengrig +* Date: 4/7/11 +* Time: 2:44 PM +*/ +abstract class GeneralRunner implements ContinuationContext { + protected final Project myProject; + protected final boolean myCancellable; + protected final List myQueue; + protected final Object myQueueLock; + protected volatile boolean myTriggerSuspend; + protected ProgressIndicator myIndicator; + protected final Map myDisasters; + private final List> myTasksPatchers; + private final Map, Consumer> myHandlersMap; + + GeneralRunner(final Project project, boolean cancellable) { + myProject = project; + myCancellable = cancellable; + myQueueLock = new Object(); + myQueue = new LinkedList(); + myDisasters = new HashMap(); + myHandlersMap = new HashMap, Consumer>(); + myTasksPatchers = new ArrayList>(); + } + + public void addExceptionHandler(final Class clazz, final Consumer consumer) { + synchronized (myQueueLock) { + myHandlersMap.put(clazz, new Consumer() { + @Override + public void consume(Exception e) { + if (!clazz.isAssignableFrom(e.getClass())) { + throw new RuntimeException(e); + } + consumer.consume((T)e); + } + }); + } + } + + public Project getProject() { + return myProject; + } + + public void clearDisasters() { + synchronized (myQueueLock) { + myDisasters.clear(); + } + } + + @Override + public boolean handleException(Exception e) { + synchronized (myQueueLock) { + final Class aClass = e.getClass(); + Consumer consumer = myHandlersMap.get(e.getClass()); + if (consumer != null) { + consumer.consume(e); + return true; + } + for (Map.Entry, Consumer> entry : myHandlersMap.entrySet()) { + if (entry.getKey().isAssignableFrom(aClass)) { + entry.getValue().consume(e); + return true; + } + } + } + return false; + } + + @CalledInAny + public void cancelEverything() { + synchronized (myQueueLock) { + myQueue.clear(); + myIndicator = null; + } + } + + public void cancelCurrent() { + if (myIndicator != null) { + myIndicator.cancel(); + } + } + + public void suspend() { + myTriggerSuspend = true; + } + + @Override + public void keepExisting(Object disaster, Object cure) { + synchronized (myQueueLock) { + for (TaskDescriptor taskDescriptor : myQueue) { + taskDescriptor.addCure(disaster, cure); + } + } + } + + @Override + public void throwDisaster(@NotNull Object disaster, @NotNull final Object cure) { + synchronized (myQueueLock) { + final Iterator iterator = myQueue.iterator(); + while (iterator.hasNext()) { + final TaskDescriptor taskDescriptor = iterator.next(); + if (taskDescriptor.isHaveMagicCure()) continue; + final Object taskCure = taskDescriptor.hasCure(disaster); + if (! cure.equals(taskCure)) { + iterator.remove(); + } + } + myDisasters.put(disaster, cure); + } + } + + @Override + public void after(@NotNull TaskDescriptor inQueue, TaskDescriptor... next) { + synchronized (myQueueLock) { + int idx = -1; + int i = 0; + for (TaskDescriptor descriptor : myQueue) { + if (descriptor == inQueue) { + idx = i; + break; + } + ++ i; + } + assert idx != -1; + final List asList = Arrays.asList(next); + patchTasks(asList); + myQueue.addAll(idx + 1, asList); + } + } + + private void patchTasks(final List next) { + for (TaskDescriptor descriptor : next) { + for (Consumer tasksPatcher : myTasksPatchers) { + tasksPatcher.consume(descriptor); + } + } + } + + @CalledInAny + public void next(TaskDescriptor... next) { + synchronized (myQueueLock) { + final List asList = Arrays.asList(next); + patchTasks(asList); + myQueue.addAll(0, asList); + } + } + + public void next(List next) { + synchronized (myQueueLock) { + patchTasks(next); + myQueue.addAll(0, next); + } + } + + @Override + public void last(List next) { + synchronized (myQueueLock) { + patchTasks(next); + myQueue.addAll(next); + } + } + + @Override + public void last(TaskDescriptor... next) { + synchronized (myQueueLock) { + final List asList = Arrays.asList(next); + patchTasks(asList); + myQueue.addAll(asList); + } + } + + public boolean isEmpty() { + synchronized (myQueueLock) { + return myQueue.isEmpty(); + } + } + + public abstract void ping(); + + @Override + public void addNewTasksPatcher(@NotNull Consumer consumer) { + synchronized (myQueueLock) { + myTasksPatchers.add(consumer); + } + } + + @Override + public void removeNewTasksPatcher(@NotNull Consumer consumer) { + synchronized (myQueueLock) { + myTasksPatchers.remove(consumer); + } + } + + + @CalledInAwt + public void onCancel() { + // left only "final" tasks + synchronized (myQueueLock) { + if (myQueue.isEmpty()) return; + final Iterator iterator = myQueue.iterator(); + while (iterator.hasNext()) { + final TaskDescriptor next = iterator.next(); + if (! next.isHaveMagicCure()) { + iterator.remove(); + } + } + } + + ping(); + } + + // null - no more tasks or suspend + @Nullable + protected TaskDescriptor getNextMatching() { + while (true) { + synchronized (myQueueLock) { + if (myQueue.isEmpty()) return null; + if (myTriggerSuspend) { + return null; + } + TaskDescriptor current = myQueue.remove(0); + // check if some tasks were scheduled after disaster was thrown, anyway, they should also be checked for cure + if (! current.isHaveMagicCure()) { + if (myIndicator != null && myIndicator.isCanceled()) { + current = null; + continue; + } else { + for (Map.Entry entry : myDisasters.entrySet()) { + if (! entry.getValue().equals(current.hasCure(entry.getKey()))) { + current = null; + break; + } + } + } + } + if (current != null) return current; + } + } + } +} diff --git a/platform/vcs-impl/src/com/intellij/util/continuation/SameProgressRunner.java b/platform/vcs-impl/src/com/intellij/util/continuation/SameProgressRunner.java new file mode 100644 index 000000000000..6757b3dc7eb3 --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/util/continuation/SameProgressRunner.java @@ -0,0 +1,106 @@ +/* + * Copyright 2000-2011 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 com.intellij.util.continuation; + +import com.intellij.CommonBundle; +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.EmptyProgressIndicator; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.vcs.changes.BackgroundFromStartOption; +import com.intellij.util.WaitForProgressToShow; +import com.intellij.util.concurrency.Semaphore; +import sun.util.LocaleServiceProviderPool; + +/** + * @author irengrig + * Date: 4/7/11 + * Time: 2:54 PM + */ +public class SameProgressRunner extends GeneralRunner { + private final Thread myInitThread; + private final Semaphore mySemaphore; + private final static Logger LOG = Logger.getInstance("#com.intellij.util.continuation.SameProgressRunner"); + + public SameProgressRunner(Project project, boolean cancellable, final String commonName) { + super(project, cancellable); + + final Application application = ApplicationManager.getApplication(); + if (! application.isUnitTestMode()) { + assert ! application.isDispatchThread(); + } + myIndicator = ProgressManager.getInstance().getProgressIndicator(); + if (myIndicator == null) { + myIndicator = new EmptyProgressIndicator(); + } + myInitThread = Thread.currentThread(); + mySemaphore = new Semaphore(); + } + + @Override + public void ping() { + myTriggerSuspend = false; + if (Thread.currentThread().equals(myInitThread)) { + pingInSourceThread(); + } else { + mySemaphore.up(); + } + } + + private void pingInSourceThread() { + while (true) { + try { + // stop if project is being disposed + if (! myProject.isOpen()) return; + + final TaskDescriptor current = getNextMatching(); + if (current == null) { + synchronized (myQueueLock) { + if (myTriggerSuspend) { + mySemaphore.down(); + while (myTriggerSuspend) { + mySemaphore.waitFor(500); + } + continue; + } else { + return; + } + } + } + + if (Where.AWT.equals(current.getWhere())) { + WaitForProgressToShow.runOrInvokeAndWaitAboveProgress(new Runnable() { + @Override + public void run() { + current.run(SameProgressRunner.this); + } + }); + } else { + current.run(this); + } + } catch (ProcessCanceledException ignored) { + } catch (Throwable t) { + LOG.error(t); + myIndicator.cancel(); + } + } + } +} diff --git a/platform/vcs-impl/src/com/intellij/util/continuation/SeparatePiecesRunner.java b/platform/vcs-impl/src/com/intellij/util/continuation/SeparatePiecesRunner.java new file mode 100644 index 000000000000..4a2db982823e --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/util/continuation/SeparatePiecesRunner.java @@ -0,0 +1,108 @@ +/* + * Copyright 2000-2011 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 com.intellij.util.continuation; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.progress.EmptyProgressIndicator; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator; +import com.intellij.openapi.progress.impl.ProgressManagerImpl; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.CalledInAwt; +import com.intellij.openapi.vcs.changes.BackgroundFromStartOption; +import com.intellij.util.WaitForProgressToShow; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author irengrig + * Date: 4/7/11 + * Time: 2:46 PM + */ +public class SeparatePiecesRunner extends GeneralRunner { + public SeparatePiecesRunner(Project project, boolean cancellable) { + super(project, cancellable); + } + + @CalledInAwt + public void ping() { + if (! ApplicationManager.getApplication().isDispatchThread()) { + WaitForProgressToShow.runOrInvokeLaterAboveProgress(new Runnable() { + public void run() { + pingImpl(); + } + }, null, myProject); + } else { + pingImpl(); + } + } + + private void pingImpl() { + while (true) { + // stop if project is being disposed + if (! myProject.isOpen()) return; + final TaskDescriptor current = getNextMatching(); + if (current == null) { + synchronized (myQueueLock) { + myTriggerSuspend = false; + } + return; + } + + if (Where.AWT.equals(current.getWhere())) { + myIndicator = null; + current.run(this); + } else { + final TaskWrapper task = new TaskWrapper(myProject, current.getName(), myCancellable, current, this); + myIndicator = ApplicationManager.getApplication().isUnitTestMode() ? new EmptyProgressIndicator() : + new BackgroundableProcessIndicator(task); + ProgressManagerImpl.runProcessWithProgressAsynchronously(task, myIndicator); + return; + } + } + } + + static class TaskWrapper extends Task.Backgroundable { + private final TaskDescriptor myTaskDescriptor; + private final GeneralRunner myGeneralRunner; + + TaskWrapper(@Nullable Project project, + @NotNull String title, + boolean canBeCancelled, + TaskDescriptor taskDescriptor, + GeneralRunner generalRunner) { + super(project, title, canBeCancelled, BackgroundFromStartOption.getInstance()); + myTaskDescriptor = taskDescriptor; + myGeneralRunner = generalRunner; + } + + @Override + public void run(@NotNull ProgressIndicator indicator) { + myTaskDescriptor.run(myGeneralRunner); + } + + @Override + public void onSuccess() { + myGeneralRunner.ping(); + } + + @Override + public void onCancel() { + myGeneralRunner.onCancel(); + } + } +} diff --git a/plugins/git4idea/src/git4idea/checkin/GitPushActiveBranchesDialog.java b/plugins/git4idea/src/git4idea/checkin/GitPushActiveBranchesDialog.java index 13bd919d27bf..ff28bbc772f4 100644 --- a/plugins/git4idea/src/git4idea/checkin/GitPushActiveBranchesDialog.java +++ b/plugins/git4idea/src/git4idea/checkin/GitPushActiveBranchesDialog.java @@ -20,7 +20,6 @@ import com.intellij.notification.NotificationType; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.progress.EmptyProgressIndicator; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; @@ -37,7 +36,6 @@ import com.intellij.ui.CheckboxTree; import com.intellij.ui.CheckedTreeNode; import com.intellij.ui.ColoredTreeCellRenderer; import com.intellij.ui.SimpleTextAttributes; -import com.intellij.util.Consumer; import com.intellij.util.continuation.ContinuationContext; import com.intellij.util.text.DateFormatUtil; import com.intellij.util.ui.UIUtil; @@ -72,7 +70,8 @@ import java.util.*; import java.util.List; import java.util.concurrent.atomic.AtomicReference; -import static git4idea.ui.GitUIUtil.*; +import static git4idea.ui.GitUIUtil.notifyError; +import static git4idea.ui.GitUIUtil.notifyMessage; /** * The dialog that allows pushing active branches. @@ -304,8 +303,8 @@ public class GitPushActiveBranchesDialog extends DialogWrapper { final ArrayList errors = new ArrayList(); for (Root r : rootsToPush) { GitLineHandler h = new GitLineHandler(myProject, r.root, GitCommand.PUSH); - String src = r.commitToPush != null ? r.commitToPush : r.branch; - h.addParameters("-v", r.remote, src + ":" + r.remoteBranch); + String src = r.commitToPush != null ? r.commitToPush : r.currentBranch; + h.addParameters("-v", r.remoteName, src + ":" + r.remoteBranch); GitPushUtils.trackPushRejectedAsError(h, "Rejected push (" + r.root.getPresentableUrl() + "): "); errors.addAll(GitHandlerUtil.doSynchronouslyWithExceptions(h)); } @@ -321,7 +320,7 @@ public class GitPushActiveBranchesDialog extends DialogWrapper { for (int i = 0; i < myTreeRoot.getChildCount(); i++) { CheckedTreeNode node = (CheckedTreeNode) myTreeRoot.getChildAt(i); Root r = (Root)node.getUserObject(); - if (r.remote == null || r.commits.size() == 0) { + if (r.remoteName == null || r.commits.size() == 0) { continue; } boolean topCommit = true; @@ -646,7 +645,7 @@ public class GitPushActiveBranchesDialog extends DialogWrapper { for (Commit c : r.commits) { CheckedTreeNode child = new CheckedTreeNode(c); rootNode.add(child); - child.setChecked(r.remote != null && !unchecked.contains(c.commitId())); + child.setChecked(r.remoteName != null && !unchecked.contains(c.commitId())); } myTreeRoot.add(rootNode); } @@ -704,7 +703,7 @@ public class GitPushActiveBranchesDialog extends DialogWrapper { error = GitBundle.getString("push.active.error.reorder.needed"); } } - if (r.branch == null) { + if (r.currentBranch == null) { if (error == null) { error = GitBundle.getString("push.active.error.no.branch"); } @@ -770,20 +769,20 @@ public class GitPushActiveBranchesDialog extends DialogWrapper { r.root = root; GitBranch b = GitBranch.current(project, root); if (b != null) { - r.branch = b.getFullName(); - r.remote = b.getTrackedRemoteName(project, root); + r.currentBranch = b.getFullName(); + r.remoteName = b.getTrackedRemoteName(project, root); r.remoteBranch = b.getTrackedBranchName(project, root); - if (r.remote != null) { - if (fetchData && !r.remote.equals(".")) { + if (r.remoteName != null) { + if (fetchData && !r.remoteName.equals(".")) { GitLineHandler fetch = new GitLineHandler(project, root, GitCommand.FETCH); - fetch.addParameters(r.remote, "-v"); + fetch.addParameters(r.remoteName, "-v"); Collection exs = GitHandlerUtil.doSynchronouslyWithExceptions(fetch); exceptions.addAll(exs); } GitBranch tracked = b.tracked(project, root); assert tracked != null : "Tracked branch cannot be null here"; GitSimpleHandler unmerged = new GitSimpleHandler(project, root, GitCommand.LOG); - unmerged.addParameters("--pretty=format:%H", r.branch + ".." + tracked.getFullName()); + unmerged.addParameters("--pretty=format:%H", r.currentBranch + ".." + tracked.getFullName()); unmerged.setNoSSH(true); unmerged.setStdoutSuppressed(true); StringScanner su = new StringScanner(unmerged.run()); @@ -793,7 +792,7 @@ public class GitPushActiveBranchesDialog extends DialogWrapper { } } GitSimpleHandler toPush = new GitSimpleHandler(project, root, GitCommand.LOG); - toPush.addParameters("--pretty=format:%H%x20%ct%x20%at%x20%s%n%P", tracked.getFullName() + ".." + r.branch); + toPush.addParameters("--pretty=format:%H%x20%ct%x20%at%x20%s%n%P", tracked.getFullName() + ".." + r.currentBranch); toPush.setNoSSH(true); toPush.setStdoutSuppressed(true); StringScanner sp = new StringScanner(toPush.run()); @@ -907,77 +906,59 @@ public class GitPushActiveBranchesDialog extends DialogWrapper { static abstract class Node { /** * Render the node text - * - * @param renderer the renderer to use */ protected abstract void render(ColoredTreeCellRenderer renderer); } - /** - * The commit descriptor - */ static class Status extends Node { - /** - * The root - */ Root root; + private String myMessage = ""; - /** - * {@inheritDoc} - */ @Override protected void render(ColoredTreeCellRenderer renderer) { renderer.append(GitBundle.getString("push.active.status.status")); - if (root.branch == null) { - renderer.append(GitBundle.message("push.active.status.no.branch"), SimpleTextAttributes.ERROR_ATTRIBUTES); + if (root.currentBranch == null) { + myMessage = GitBundle.message("push.active.status.no.branch"); + renderer.append(myMessage, SimpleTextAttributes.ERROR_ATTRIBUTES); } - else if (root.remote == null) { - renderer.append(GitBundle.message("push.active.status.no.tracked"), SimpleTextAttributes.GRAYED_BOLD_ATTRIBUTES); + else if (root.remoteName == null) { + myMessage = GitBundle.message("push.active.status.no.tracked"); + renderer.append(myMessage, SimpleTextAttributes.GRAYED_BOLD_ATTRIBUTES); } else if (root.remoteCommits != 0 && root.commits.size() == 0) { - renderer.append(GitBundle.message("push.active.status.no.commits.behind", root.remoteCommits), - SimpleTextAttributes.GRAYED_BOLD_ATTRIBUTES); + myMessage = GitBundle.message("push.active.status.no.commits.behind", root.remoteCommits); + renderer.append(myMessage, SimpleTextAttributes.GRAYED_BOLD_ATTRIBUTES); } else if (root.commits.size() == 0) { - renderer.append(GitBundle.message("push.active.status.no.commits"), SimpleTextAttributes.GRAYED_BOLD_ATTRIBUTES); + myMessage = GitBundle.message("push.active.status.no.commits"); + renderer.append(myMessage, SimpleTextAttributes.GRAYED_BOLD_ATTRIBUTES); } else if (root.remoteCommits != 0) { - renderer.append(GitBundle.message("push.active.status.behind", root.remoteCommits), SimpleTextAttributes.ERROR_ATTRIBUTES); + myMessage = GitBundle.message("push.active.status.behind", root.remoteCommits); + renderer.append(myMessage, SimpleTextAttributes.ERROR_ATTRIBUTES); } else { - renderer.append(GitBundle.message("push.active.status.push", root.commits.size())); + myMessage = GitBundle.message("push.active.status.push", root.commits.size()); + renderer.append(myMessage); } } + + @Override + public String toString() { + return GitBundle.getString("push.active.status.status") + myMessage; + } } /** * The commit descriptor */ static class Commit extends Node { - /** - * The root - */ Root root; - /** - * The revision - */ GitRevisionNumber revision; - /** - * The message - */ String message; - /** - * The author time - */ String authorTime; - /** - * If true, the commit is a merge - */ - boolean isMerge; + boolean isMerge; // true if this commit is a merge commit - /** - * {@inheritDoc} - */ @Override protected void render(ColoredTreeCellRenderer renderer) { renderer.append(revision.asString().substring(0, HASH_PREFIX_SIZE), SimpleTextAttributes.GRAYED_ATTRIBUTES); @@ -994,53 +975,35 @@ public class GitPushActiveBranchesDialog extends DialogWrapper { String commitId() { return authorTime + ":" + message; } + + @Override + public String toString() { + String mergeCommitStr = isMerge ? " " + GitBundle.getString("push.active.commit.node.merge") : ""; + return revision.toShortString() + " " + message + mergeCommitStr; + } } /** * The root node */ static class Root extends Node { - /** - * if true, the update is required - */ int remoteCommits; - /** - * the path to vcs root - */ VirtualFile root; - /** - * the current branch - */ - String branch; - /** - * the remote name - */ - String remote; - /** - * the remote branch name - */ + String currentBranch; + String remoteName; String remoteBranch; - /** - * The commit that will be actually pushed - */ - String commitToPush; - /** - * the commit - */ + String commitToPush; // The commit that will be actually pushed List commits = new ArrayList(); - /** - * {@inheritDoc} - */ @Override protected void render(ColoredTreeCellRenderer renderer) { SimpleTextAttributes rootAttributes; SimpleTextAttributes branchAttributes; - if (remote != null && commits.size() != 0 && remoteCommits != 0 || branch == null) { + if (remoteName != null && commits.size() != 0 && remoteCommits != 0 || currentBranch == null) { rootAttributes = SimpleTextAttributes.ERROR_ATTRIBUTES.derive(SimpleTextAttributes.STYLE_BOLD, null, null, null); branchAttributes = SimpleTextAttributes.ERROR_ATTRIBUTES; } - else if (remote == null || commits.size() == 0) { + else if (remoteName == null || commits.size() == 0) { rootAttributes = SimpleTextAttributes.GRAYED_BOLD_ATTRIBUTES; branchAttributes = SimpleTextAttributes.GRAYED_ATTRIBUTES; } @@ -1049,13 +1012,27 @@ public class GitPushActiveBranchesDialog extends DialogWrapper { rootAttributes = SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES; } renderer.append(root.getPresentableUrl(), rootAttributes); - if (branch != null) { - renderer.append(" [" + branch, branchAttributes); - if (remote != null) { - renderer.append(" -> " + remote + "#" + remoteBranch, branchAttributes); + if (currentBranch != null) { + renderer.append(" [" + currentBranch, branchAttributes); + if (remoteName != null) { + renderer.append(" -> " + remoteName + "#" + remoteBranch, branchAttributes); } renderer.append("]", branchAttributes); } } + + @Override + public String toString() { + final StringBuilder string = new StringBuilder(); + string.append(root.getPresentableUrl()); + if (currentBranch != null) { + string.append(" [").append(currentBranch); + if (remoteName != null) { + string.append(" -> ").append(remoteName).append("#").append(remoteBranch); + } + string.append("]"); + } + return string.toString(); + } } } diff --git a/plugins/git4idea/src/git4idea/checkout/branches/GitCheckoutProcess.java b/plugins/git4idea/src/git4idea/checkout/branches/GitCheckoutProcess.java index 4c930c8f837b..f0616594052a 100644 --- a/plugins/git4idea/src/git4idea/checkout/branches/GitCheckoutProcess.java +++ b/plugins/git4idea/src/git4idea/checkout/branches/GitCheckoutProcess.java @@ -36,7 +36,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.ToolWindowManager; import com.intellij.util.Consumer; import com.intellij.util.continuation.Continuation; -import com.intellij.util.continuation.ContinuationContext; +import com.intellij.util.continuation.GatheringContinuationContext; import com.intellij.util.ui.UIUtil; import git4idea.GitRevisionNumber; import git4idea.GitUtil; @@ -578,7 +578,7 @@ public class GitCheckoutProcess { progress.setText("Refreshing files before restoring shelve: " + shelve.DESCRIPTION); final ShelvedChangeList finalShelve = shelve; - final Continuation continuation = new Continuation(myProject, true); + final Continuation continuation = Continuation.createForCurrentProgress(myProject, true, "Git: restore changes"); final Consumer exceptionConsumer = new Consumer() { @Override public void consume(VcsException e) { @@ -590,8 +590,7 @@ public class GitCheckoutProcess { } }; continuation.addExceptionHandler(VcsException.class, exceptionConsumer); - final ContinuationContext.GatheringContinuationContext initContext = - new ContinuationContext.GatheringContinuationContext(); + final GatheringContinuationContext initContext = new GatheringContinuationContext(); GitStashUtils.doSystemUnshelve(myProject, shelve, myShelveManager, new Runnable() { @Override public void run() { diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/LoadAlgorithm.java b/plugins/git4idea/src/git4idea/history/wholeTree/LoadAlgorithm.java index 6aa7facde4a7..e275a048ba3d 100644 --- a/plugins/git4idea/src/git4idea/history/wholeTree/LoadAlgorithm.java +++ b/plugins/git4idea/src/git4idea/history/wholeTree/LoadAlgorithm.java @@ -15,10 +15,7 @@ package git4idea.history.wholeTree; import com.intellij.openapi.project.Project; import com.intellij.openapi.vcs.CalledInAwt; import com.intellij.util.containers.Convertor; -import com.intellij.util.continuation.Continuation; -import com.intellij.util.continuation.ContinuationContext; -import com.intellij.util.continuation.TaskDescriptor; -import com.intellij.util.continuation.Where; +import com.intellij.util.continuation.*; import org.jetbrains.annotations.Nullable; import java.util.List; @@ -45,8 +42,7 @@ public class LoadAlgorithm { } public void fillContinuation() { - final ContinuationContext.GatheringContinuationContext initContext = - new ContinuationContext.GatheringContinuationContext(); + final GatheringContinuationContext initContext = new GatheringContinuationContext(); for (LoaderAndRefresher loader : myLoaders) { final LoaderFactory factory = new LoaderFactory(loader); diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/LoadController.java b/plugins/git4idea/src/git4idea/history/wholeTree/LoadController.java index 84d94875763d..e017e6cf73a7 100644 --- a/plugins/git4idea/src/git4idea/history/wholeTree/LoadController.java +++ b/plugins/git4idea/src/git4idea/history/wholeTree/LoadController.java @@ -86,7 +86,7 @@ public class LoadController implements Loader { continuation = myPreviousAlgorithm.getContinuation(); continuation.clearQueue(); } else { - continuation = new Continuation(myProject, true); + continuation = Continuation.createFragmented(myProject, true); } myPreviousAlgorithm = new LoadAlgorithm(myProject, list, shortLoaders, continuation); myPreviousAlgorithm.fillContinuation(); diff --git a/plugins/git4idea/src/git4idea/update/GitUpdateLikeProcess.java b/plugins/git4idea/src/git4idea/update/GitUpdateLikeProcess.java index 91469e800a43..dc357f6dcc83 100644 --- a/plugins/git4idea/src/git4idea/update/GitUpdateLikeProcess.java +++ b/plugins/git4idea/src/git4idea/update/GitUpdateLikeProcess.java @@ -18,12 +18,10 @@ package git4idea.update; import com.intellij.ide.GeneralSettings; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ex.ProjectManagerEx; -import com.intellij.util.continuation.Continuation; -import com.intellij.util.continuation.ContinuationContext; -import com.intellij.util.continuation.TaskDescriptor; -import com.intellij.util.continuation.Where; +import com.intellij.util.continuation.*; import com.intellij.util.ui.UIUtil; /** @@ -32,6 +30,7 @@ import com.intellij.util.ui.UIUtil; * Time: 2:59 PM */ public abstract class GitUpdateLikeProcess { + public static final String GIT_UPDATING = "Git: updating"; private final Project myProject; private GeneralSettings myGeneralSettings; private ProjectManagerEx myProjectManager; @@ -60,22 +59,25 @@ public abstract class GitUpdateLikeProcess { } }); - final Continuation continuation = new Continuation(myProject, true); - final ContinuationContext.GatheringContinuationContext initContext = new ContinuationContext.GatheringContinuationContext(); - initContext.next(new TaskDescriptor("Git: updating", Where.POOLED) { - @Override - public void run(final ContinuationContext context) { - runImpl(context); - } - }, new TaskDescriptor("", Where.AWT) { + final Continuation continuation = Continuation.createForCurrentProgress(myProject, true, GIT_UPDATING); + final GatheringContinuationContext initContext = new GatheringContinuationContext(); + final TaskDescriptor returnFlagsBack = new TaskDescriptor("", Where.AWT) { @Override public void run(ContinuationContext context) { myProjectManager.unblockReloadingProjectOnExternalChanges(); myGeneralSettings.setSaveOnFrameDeactivation(saveOnFrameDeactivation); myGeneralSettings.setSyncOnFrameActivation(syncOnFrameDeactivation); } - }); - continuation.runAndWait(initContext.getList()); + }; + final TaskDescriptor[] next = {new TaskDescriptor(GIT_UPDATING, Where.POOLED) { + @Override + public void run(final ContinuationContext context) { + runImpl(context); + } + }, returnFlagsBack}; + returnFlagsBack.setHaveMagicCure(true); + initContext.next(next); + continuation.run(initContext.getList()); } protected abstract void runImpl(ContinuationContext context); diff --git a/plugins/git4idea/src/git4idea/update/GitUpdateProcess.java b/plugins/git4idea/src/git4idea/update/GitUpdateProcess.java index 9393eb01cae8..050d53d59747 100644 --- a/plugins/git4idea/src/git4idea/update/GitUpdateProcess.java +++ b/plugins/git4idea/src/git4idea/update/GitUpdateProcess.java @@ -27,6 +27,8 @@ import com.intellij.openapi.vcs.update.UpdatedFiles; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.Consumer; import com.intellij.util.continuation.ContinuationContext; +import com.intellij.util.continuation.ContinuationFinalTasksInserter; +import com.intellij.util.continuation.TaskDescriptor; import com.intellij.util.text.DateFormatUtil; import git4idea.GitBranch; import git4idea.branch.GitBranchPair; @@ -189,7 +191,11 @@ public class GitUpdateProcess { "Restoring changes saved before update failed with an error.
" + e.getLocalizedMessage()); } }); + // try restore changes under all circumstances + final ContinuationFinalTasksInserter finalTasksInserter = new ContinuationFinalTasksInserter(context); + finalTasksInserter.allNextAreFinal(); mySaver.restoreLocalChanges(context); + finalTasksInserter.removeFinalPropertyAdder(); } // fetch all roots. If an error happens, return false and notify about errors. diff --git a/plugins/hg4idea/resources/org/zmlx/hg4idea/HgVcsMessages.properties b/plugins/hg4idea/resources/org/zmlx/hg4idea/HgVcsMessages.properties index e7051d7106a5..b2f5d4db3c7c 100644 --- a/plugins/hg4idea/resources/org/zmlx/hg4idea/HgVcsMessages.properties +++ b/plugins/hg4idea/resources/org/zmlx/hg4idea/HgVcsMessages.properties @@ -116,5 +116,6 @@ hg4idea.merge.please-commit=Merged heads, please commit repository \"{0}\" hg4idea.error.invalidExecutable=\"{0}\" is not a valid mercurial executable hg4idea.integrate.other.head=Other head: {0} -hgidea.dialog.login.password.required=Login and password required +hg4idea.dialog.login.password.required=Login and password required +hg4idea.dialog.login.description=Login to {0} hg4idea.exception.file.not.under.hg=The file {0} is not under Mercurial. \ No newline at end of file diff --git a/plugins/hg4idea/resources/python/LICENSE.txt b/plugins/hg4idea/resources/python/LICENSE.txt new file mode 100644 index 000000000000..d159169d1050 --- /dev/null +++ b/plugins/hg4idea/resources/python/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/plugins/hg4idea/resources/python/README.txt b/plugins/hg4idea/resources/python/README.txt new file mode 100644 index 000000000000..7f27d21d4950 --- /dev/null +++ b/plugins/hg4idea/resources/python/README.txt @@ -0,0 +1,8 @@ +This Mercurial extension is meant to provide robust integration with +external tools. + +Mercurial sometimes asks vital questions during several operations, for +instance during merging. Automatically choosing the default answer +for these questions is typically not what you want when working from, +for instance, an IDE. This extension forwards the prompts to another +process (i.e. the IDE) so that it can be answered by the user. \ No newline at end of file diff --git a/plugins/hg4idea/resources/python/prompthooks.py b/plugins/hg4idea/resources/python/prompthooks.py index 9641d0cb6fdb..2507a9daac14 100644 --- a/plugins/hg4idea/resources/python/prompthooks.py +++ b/plugins/hg4idea/resources/python/prompthooks.py @@ -1,24 +1,50 @@ #!/usr/bin/env python -from mercurial import filemerge, ui, util, dispatch -from mercurial.node import short -import sys, struct, socket +#Mercurial extension to robustly integrate prompts with other processes +#Copyright (C) 2010-2011 Willem Verstraeten +# +#This program is free software; you can redistribute it and/or +#modify it under the terms of the GNU General Public License +#as published by the Free Software Foundation; either version 2 +#of the License, or (at your option) any later version. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. +# +#You should have received a copy of the GNU General Public License +#along with this program; if not, write to the Free Software +#Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +import urllib2 +from mercurial import ui, util +import struct, socket +from mercurial.i18n import _ + +try: + from mercurial.url import passwordmgr +except: + from mercurial.httprepo import passwordmgr def sendInt( client, number): length = struct.pack('>L', number) client.sendall( length ) def send( client, data ): - sendInt(client, len(data)) - client.sendall( data ) + if data is None: + sendInt(client, 0) + else: + sendInt(client, len(data)) + client.sendall( data ) -def receiveInt(client): +def receiveIntWithMessage(client, message): requiredLength = struct.calcsize('>L') buffer = '' while len(buffer) - diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgChange.java b/plugins/hg4idea/src/org/zmlx/hg4idea/HgChange.java similarity index 96% rename from plugins/hg4idea/src/org/zmlx/hg4idea/command/HgChange.java rename to plugins/hg4idea/src/org/zmlx/hg4idea/HgChange.java index e9c8af2c5145..0a086d416e14 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgChange.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/HgChange.java @@ -10,11 +10,10 @@ // 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.zmlx.hg4idea.command; +package org.zmlx.hg4idea; import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.HashCodeBuilder; -import org.zmlx.hg4idea.HgFile; public final class HgChange { diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/HgErrorHandler.java b/plugins/hg4idea/src/org/zmlx/hg4idea/HgErrorHandler.java index f9b10b4e1a7f..e80373f66032 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/HgErrorHandler.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/HgErrorHandler.java @@ -13,7 +13,7 @@ package org.zmlx.hg4idea; import com.intellij.openapi.vcs.VcsException; -import org.zmlx.hg4idea.command.HgCommandResult; +import org.zmlx.hg4idea.execution.HgCommandResult; public abstract class HgErrorHandler { diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgErrorUtil.java b/plugins/hg4idea/src/org/zmlx/hg4idea/HgErrorUtil.java similarity index 91% rename from plugins/hg4idea/src/org/zmlx/hg4idea/command/HgErrorUtil.java rename to plugins/hg4idea/src/org/zmlx/hg4idea/HgErrorUtil.java index d5f9abc8a171..a9c087df5035 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgErrorUtil.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/HgErrorUtil.java @@ -10,10 +10,11 @@ // 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.zmlx.hg4idea.command; +package org.zmlx.hg4idea; import org.apache.commons.lang.StringUtils; import org.jetbrains.annotations.Nullable; +import org.zmlx.hg4idea.execution.HgCommandResult; import java.util.List; @@ -29,7 +30,7 @@ public final class HgErrorUtil { return StringUtils.isNotBlank(line) && StringUtils.contains(line, "abort:"); } - static boolean isAuthorizationError(HgCommandResult result) { + public static boolean isAuthorizationError(HgCommandResult result) { if (result == null) { return true; } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgFileStatusEnum.java b/plugins/hg4idea/src/org/zmlx/hg4idea/HgFileStatusEnum.java similarity index 96% rename from plugins/hg4idea/src/org/zmlx/hg4idea/command/HgFileStatusEnum.java rename to plugins/hg4idea/src/org/zmlx/hg4idea/HgFileStatusEnum.java index 5025cd731d3b..96674e4609a2 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgFileStatusEnum.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/HgFileStatusEnum.java @@ -10,7 +10,7 @@ // 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.zmlx.hg4idea.command; +package org.zmlx.hg4idea; public enum HgFileStatusEnum { diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/HgGlobalSettings.java b/plugins/hg4idea/src/org/zmlx/hg4idea/HgGlobalSettings.java index 6c73c9dca1cc..502b0f234908 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/HgGlobalSettings.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/HgGlobalSettings.java @@ -20,9 +20,7 @@ import org.apache.commons.lang.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; +import java.util.*; @State( name = "HgGlobalSettings", @@ -35,8 +33,8 @@ public class HgGlobalSettings implements PersistentStateComponent list of logins for this URL. Passwords are remembered in the PasswordSafe. - private Map> myRememberedUrls = new HashMap>(); + // visited URL -> login for this URL. Passwords are remembered in the PasswordSafe. + private Map myRememberedUserNames = new HashMap(); private boolean myRunViaBash; public static class State { @@ -57,12 +55,13 @@ public class HgGlobalSettings implements PersistentStateComponent> getRememberedUrls() { - return myRememberedUrls; + @Nullable + public String getRememberedUserName(@NotNull String stringUrl) { + return myRememberedUserNames.get(stringUrl); } /** @@ -77,12 +76,7 @@ public class HgGlobalSettings implements PersistentStateComponent list = myRememberedUrls.get(stringUrl); - if (list == null) { - list = new LinkedList(); - myRememberedUrls.put(stringUrl, list); - } - list.add(username); + myRememberedUserNames.put(stringUrl, username); } public static String getDefaultExecutable() { diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/HgRemoteStatusUpdater.java b/plugins/hg4idea/src/org/zmlx/hg4idea/HgRemoteStatusUpdater.java index ec43819f9017..935f87cea32e 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/HgRemoteStatusUpdater.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/HgRemoteStatusUpdater.java @@ -68,7 +68,7 @@ class HgRemoteStatusUpdater implements HgUpdater { } }.queue(); } - }, project.getDisposed()); + }); } private void updateChangesetStatus(Project project, VcsRoot[] roots, HgChangesetStatus status, boolean incoming) { diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/HgUtil.java b/plugins/hg4idea/src/org/zmlx/hg4idea/HgUtil.java index df128f97eb95..610c5281f4c3 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/HgUtil.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/HgUtil.java @@ -33,7 +33,6 @@ import com.intellij.util.containers.HashMap; import com.intellij.vcsUtil.VcsUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.zmlx.hg4idea.command.HgChange; import org.zmlx.hg4idea.command.HgRemoveCommand; import org.zmlx.hg4idea.command.HgStatusCommand; import org.zmlx.hg4idea.command.HgWorkingCopyRevisionsCommand; diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/HgVFSListener.java b/plugins/hg4idea/src/org/zmlx/hg4idea/HgVFSListener.java index 7ad4704f9206..0f98a89c5eca 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/HgVFSListener.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/HgVFSListener.java @@ -15,6 +15,7 @@ */ package org.zmlx.hg4idea; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; @@ -28,6 +29,7 @@ import org.jetbrains.annotations.NotNull; import org.zmlx.hg4idea.command.*; import java.util.*; +import java.util.concurrent.atomic.AtomicReference; /** * Listens to VFS events (such as adding or deleting bunch of files) and performs necessary operations with the VCS. @@ -142,56 +144,81 @@ public class HgVFSListener extends VcsVFSListener { return HgVcsMessages.message("hg4idea.remove.single.body"); } - @Override - protected VcsDeleteType needConfirmDeletion(VirtualFile file) { - //// newly added files (which were added to the repo but never committed) should be removed from the VCS, - //// but without user confirmation. - final FilePath filePath = VcsUtil.getFilePath(file.getPath()); - final VirtualFile repo = HgUtil.getHgRootOrNull(myProject, filePath); - if (repo == null) { - return super.needConfirmDeletion(file); - } - final HgFile hgFile = new HgFile(repo, filePath); - - final HgLogCommand logCommand = new HgLogCommand(myProject); - logCommand.setLogFile(true); - logCommand.setFollowCopies(false); - logCommand.setIncludeRemoved(true); - final List localRevisions = logCommand.execute(hgFile, -1, true); - // file is newly added, if it doesn't have a history or if the last history action was deleting this file. - if (localRevisions == null || localRevisions.isEmpty() || localRevisions.get(0).getDeletedFiles().contains(hgFile.getRelativePath())) { - return VcsDeleteType.SILENT; - } - return VcsDeleteType.CONFIRM; - } - protected void executeDelete() { final List filesToDelete = new ArrayList(myDeletedWithoutConfirmFiles); - final List deletedFiles = new ArrayList(myDeletedFiles); + final List filesToConfirmDeletion = new ArrayList(myDeletedFiles); myDeletedWithoutConfirmFiles.clear(); myDeletedFiles.clear(); // skip unversioned files and files which are not under Mercurial final ChangeListManagerImpl changeListManager = ChangeListManagerImpl.getInstanceImpl(myProject); skipUnversionedAndNotUnderHg(changeListManager, filesToDelete); - skipUnversionedAndNotUnderHg(changeListManager, deletedFiles); + skipUnversionedAndNotUnderHg(changeListManager, filesToConfirmDeletion); - // confirm removal from the VCS if needed - if (myRemoveOption.getValue() != VcsShowConfirmationOption.Value.DO_NOTHING_SILENTLY) { - if (myRemoveOption.getValue() == VcsShowConfirmationOption.Value.DO_ACTION_SILENTLY || deletedFiles.isEmpty()) { - filesToDelete.addAll(deletedFiles); - } - else { - Collection filePaths = selectFilePathsToDelete(deletedFiles); - if (filePaths != null) { - filesToDelete.addAll(filePaths); + // newly added files (which were added to the repo but never committed) should be removed from the VCS, + // but without user confirmation. + new Task.ConditionalModal(myProject, + HgVcsMessages.message("hg4idea.remove.progress"), + false, + VcsConfiguration.getInstance(myProject).getAddRemoveOption()) { + @Override public void run( @NotNull ProgressIndicator indicator ) { + // move files that are not to be confirmed anyway from filesToConfirmDeletion to filesToDelete + for (Iterator it = filesToConfirmDeletion.iterator(); it.hasNext(); ) { + FilePath file = it.next(); + if (!isDeleteCofirmationNeeded(file)) { + filesToDelete.add(file); + it.remove(); + } + } + + // confirm removal from the VCS if needed + if (myRemoveOption.getValue() != VcsShowConfirmationOption.Value.DO_NOTHING_SILENTLY) { + if (myRemoveOption.getValue() == VcsShowConfirmationOption.Value.DO_ACTION_SILENTLY || filesToConfirmDeletion.isEmpty()) { + filesToDelete.addAll(filesToConfirmDeletion); + } + else { + final AtomicReference> filePaths = new AtomicReference>(); + ApplicationManager.getApplication().invokeAndWait(new Runnable() { + @Override public void run() { + filePaths.set(selectFilePathsToDelete(filesToConfirmDeletion)); + } + }, indicator.getModalityState()); + if (filePaths.get() != null) { + filesToDelete.addAll(filePaths.get()); + } + } + } + + if (!filesToDelete.isEmpty()) { + performDeletion(filesToDelete); } } - } + }.queue(); + } - if (!filesToDelete.isEmpty()) { - performDeletion(filesToDelete); + /** + * Newly added files (which were added to the repo but never committed) should be removed from the VCS, + * but without user confirmation. + *

NB: we don't use {@link #needConfirmDeletion(com.intellij.openapi.vfs.VirtualFile)}, + * because it is executed in EDT, while we need to access hg log, which should be done in background. Starting a Task.Modal for + * each file is ineffective, so we start it once (in {@link #executeDelete()} for all files.

localRevisions = logCommand.execute(hgFile, -1, true); + + // file is newly added, if it doesn't have a history or if the last history action was deleting this file. + return localRevisions != null && !localRevisions.isEmpty() && !localRevisions.get(0).getDeletedFiles().contains(hgFile.getRelativePath()); } /** @@ -211,30 +238,22 @@ public class HgVFSListener extends VcsVFSListener { @Override protected void performDeletion( final List filesToDelete) { - (new Task.ConditionalModal(myProject, - HgVcsMessages.message("hg4idea.remove.progress"), - false, - VcsConfiguration.getInstance(myProject).getAddRemoveOption()) { - @Override public void run( @NotNull ProgressIndicator aProgressIndicator ) { - final ArrayList deletes = new ArrayList(); - for (FilePath file : filesToDelete) { - if (file.isDirectory()) { - continue; - } - - deletes.add(new HgFile(VcsUtil.getVcsRootFor(myProject, file), file)); - } - - if (!deletes.isEmpty()) { - new HgRemoveCommand(myProject).execute(deletes); - } - - for (HgFile file : deletes) { - dirtyScopeManager.fileDirty(file.toFilePath()); - } + final ArrayList deletes = new ArrayList(); + for (FilePath file : filesToDelete) { + if (file.isDirectory()) { + continue; } - }).queue(); + deletes.add(new HgFile(VcsUtil.getVcsRootFor(myProject, file), file)); + } + + if (!deletes.isEmpty()) { + new HgRemoveCommand(myProject).execute(deletes); + } + + for (HgFile file : deletes) { + dirtyScopeManager.fileDirty(file.toFilePath()); + } } @Override diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/HgVcs.java b/plugins/hg4idea/src/org/zmlx/hg4idea/HgVcs.java index 6aff4dfc7c5e..bf1551d06c6c 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/HgVcs.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/HgVcs.java @@ -14,6 +14,8 @@ package org.zmlx.hg4idea; import com.intellij.concurrency.JobScheduler; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ApplicationNamesInfo; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diff.impl.patch.formove.FilePathComparator; import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.fileEditor.FileEditorManagerAdapter; @@ -46,6 +48,7 @@ import com.intellij.util.containers.ComparatorDelegate; import com.intellij.util.containers.Convertor; import com.intellij.util.messages.MessageBusConnection; import com.intellij.util.messages.Topic; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.zmlx.hg4idea.provider.*; import org.zmlx.hg4idea.provider.annotate.HgAnnotationProvider; @@ -72,6 +75,7 @@ public class HgVcs extends AbstractVcs { private static final Icon INCOMING_ICON = IconLoader.getIcon("/actions/moveDown.png"); private static final Icon OUTGOING_ICON = IconLoader.getIcon("/actions/moveUp.png"); + private static final Logger LOG = Logger.getInstance(HgVcs.class); public static final String VCS_NAME = "hg4idea"; public static final String NOTIFICATION_GROUP_ID = "Mercurial"; @@ -102,6 +106,7 @@ public class HgVcs extends AbstractVcs { private final HgMergeProvider myMergeProvider; private HgExecutableValidator myExecutableValidator; private final Object myExecutableValidatorLock = new Object(); + private File myPromptHooksExtensionFile; public HgVcs(Project project, HgGlobalSettings globalSettings, HgProjectSettings projectSettings, @@ -236,12 +241,27 @@ public class HgVcs extends AbstractVcs { return HgUtil.getNearestHgRoot(dir) != null; } + /** + * @return the prompthooks.py extension used for capturing prompts from Mercurial and requesting IDEA's user about authentication. + */ + public @NotNull File getPromptHooksExtensionFile() { + if (myPromptHooksExtensionFile == null) { + // check that hooks are available + myPromptHooksExtensionFile = HgUtil.getTemporaryPythonFile("prompthooks"); + if (myPromptHooksExtensionFile == null || !myPromptHooksExtensionFile.exists()) { + LOG.error("prompthooks.py Mercurial extension is not found. Please reinstall " + ApplicationNamesInfo.getInstance().getProductName()); + } + } + return myPromptHooksExtensionFile; + } + @Override public void activate() { // validate hg executable on start if (!ApplicationManager.getApplication().isUnitTestMode()) { getExecutableValidator().checkExecutableAndShowDialogIfNeeded(); } + // status bar StatusBar statusBar = WindowManager.getInstance().getStatusBar(myProject); if (statusBar != null) { diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgAbstractGlobalAction.java b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgAbstractGlobalAction.java index d0aad5f6ba27..5a08c2c6122e 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgAbstractGlobalAction.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgAbstractGlobalAction.java @@ -22,7 +22,7 @@ import com.intellij.vcsUtil.VcsUtil; import org.jetbrains.annotations.Nullable; import org.zmlx.hg4idea.HgUtil; import org.zmlx.hg4idea.HgVcs; -import org.zmlx.hg4idea.command.HgCommandException; +import org.zmlx.hg4idea.execution.HgCommandException; import java.lang.reflect.InvocationTargetException; import java.util.Collection; diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgCommandResultNotifier.java b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgCommandResultNotifier.java index 65c83aaa04a9..21b977587f73 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgCommandResultNotifier.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgCommandResultNotifier.java @@ -15,7 +15,7 @@ package org.zmlx.hg4idea.action; import com.intellij.openapi.project.Project; import com.intellij.vcsUtil.VcsUtil; import org.apache.commons.lang.StringUtils; -import org.zmlx.hg4idea.command.HgCommandResult; +import org.zmlx.hg4idea.execution.HgCommandResult; import java.util.List; diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgCreateTagAction.java b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgCreateTagAction.java index af560c5fce2d..af702917e2e1 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgCreateTagAction.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgCreateTagAction.java @@ -14,9 +14,11 @@ package org.zmlx.hg4idea.action; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.project.Project; +import org.jetbrains.annotations.Nullable; import org.zmlx.hg4idea.command.HgTagCreateCommand; -import org.zmlx.hg4idea.command.HgCommandException; -import org.zmlx.hg4idea.command.HgCommandResult; +import org.zmlx.hg4idea.execution.HgCommandException; +import org.zmlx.hg4idea.execution.HgCommandResult; +import org.zmlx.hg4idea.execution.HgCommandResultHandler; import org.zmlx.hg4idea.ui.HgTagDialog; import java.util.Collection; @@ -44,10 +46,12 @@ public class HgCreateTagAction extends HgAbstractGlobalAction { } public void execute() throws HgCommandException { - HgCommandResult result = - new HgTagCreateCommand(project, dialog.getRepository(), dialog.getTagName()).execute(); - - new HgCommandResultNotifier(project).process(result); + new HgTagCreateCommand(project, dialog.getRepository(), dialog.getTagName()).execute(new HgCommandResultHandler() { + @Override + public void process(@Nullable HgCommandResult result) { + new HgCommandResultNotifier(project).process(result); + } + }); } }; } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgInit.java b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgInit.java index de16ba14e30f..e745a7b0085e 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgInit.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgInit.java @@ -5,7 +5,6 @@ import com.intellij.notification.NotificationType; import com.intellij.notification.Notifications; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.PlatformDataKeys; -import com.intellij.openapi.actionSystem.Presentation; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; @@ -13,6 +12,7 @@ import com.intellij.openapi.vcs.ProjectLevelVcsManager; import com.intellij.openapi.vcs.VcsDirectoryMapping; import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.Consumer; import org.zmlx.hg4idea.HgUtil; import org.zmlx.hg4idea.HgVcs; import org.zmlx.hg4idea.HgVcsMessages; @@ -53,6 +53,7 @@ public class HgInit extends DumbAwareAction { // check if the selected folder is not yet under mercurial and provide some options in that case final VirtualFile vcsRoot = HgUtil.getNearestHgRoot(selectedRoot); VirtualFile mapRoot = selectedRoot; + boolean needToCreateRepo = false; if (vcsRoot != null) { final HgInitAlreadyUnderHgDialog dialog = new HgInitAlreadyUnderHgDialog(myProject, selectedRoot.getPresentableUrl(), vcsRoot.getPresentableUrl()); @@ -64,18 +65,23 @@ public class HgInit extends DumbAwareAction { if (dialog.getAnswer() == HgInitAlreadyUnderHgDialog.Answer.USE_PARENT_REPO) { mapRoot = vcsRoot; } else if (dialog.getAnswer() == HgInitAlreadyUnderHgDialog.Answer.CREATE_REPO_HERE) { - if (!createRepository(selectedRoot)) { - return; - } + needToCreateRepo = true; } } else { // no parent repository => creating the repository here. - if (!createRepository(selectedRoot)){ - return; - } + needToCreateRepo = true; } - // update vcs directory mappings if new repository was created inside the current project directory - if (myProject != null && (! myProject.isDefault()) && myProject.getBaseDir() != null && VfsUtil.isAncestor(myProject.getBaseDir(), mapRoot, false)) { + if (needToCreateRepo) { + createRepository(selectedRoot, mapRoot); + } else { + updateDirectoryMappings(mapRoot); + } + } + + // update vcs directory mappings if new repository was created inside the current project directory + private void updateDirectoryMappings(VirtualFile mapRoot) { + if (myProject != null && (! myProject.isDefault()) && myProject.getBaseDir() != null && VfsUtil + .isAncestor(myProject.getBaseDir(), mapRoot, false)) { mapRoot.refresh(false, false); final String path = mapRoot.equals(myProject.getBaseDir()) ? "" : mapRoot.getPath(); final ProjectLevelVcsManager vcsManager = ProjectLevelVcsManager.getInstance(myProject); @@ -103,14 +109,24 @@ public class HgInit extends DumbAwareAction { } } - private boolean createRepository(VirtualFile selectedRoot) { - final boolean succeeded = (new HgInitCommand(myProject)).execute(selectedRoot); - Notifications.Bus.notify(new Notification(HgVcs.NOTIFICATION_GROUP_ID, - HgVcsMessages.message(succeeded ? "hg4idea.init.created.notification.title" : "hg4idea.init.error.title"), - HgVcsMessages.message(succeeded ? "hg4idea.init.created.notification.description" : "hg4idea.init.error.description", - selectedRoot.getPresentableUrl()), - succeeded ? NotificationType.INFORMATION : NotificationType.ERROR), myProject.isDefault() ? null : myProject); - return succeeded; + private void createRepository(final VirtualFile selectedRoot, final VirtualFile mapRoot) { + new HgInitCommand(myProject).execute(selectedRoot, new Consumer() { + @Override + public void consume(Boolean succeeded) { + if (succeeded) { + updateDirectoryMappings(mapRoot); + } + Notifications.Bus.notify(new Notification(HgVcs.NOTIFICATION_GROUP_ID, + HgVcsMessages.message( + succeeded ? "hg4idea.init.created.notification.title" : "hg4idea.init.error.title"), + HgVcsMessages.message(succeeded + ? "hg4idea.init.created.notification.description" + : "hg4idea.init.error.description", + selectedRoot.getPresentableUrl()), + succeeded ? NotificationType.INFORMATION : NotificationType.ERROR), + myProject.isDefault() ? null : myProject); + } + }); } -} +} \ No newline at end of file diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgMqRebaseAction.java b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgMqRebaseAction.java index 279d68b7b01e..82b398ca8483 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgMqRebaseAction.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgMqRebaseAction.java @@ -15,8 +15,11 @@ package org.zmlx.hg4idea.action; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import org.apache.commons.lang.StringUtils; +import org.jetbrains.annotations.Nullable; import org.zmlx.hg4idea.HgFile; import org.zmlx.hg4idea.command.*; +import org.zmlx.hg4idea.execution.HgCommandResult; +import org.zmlx.hg4idea.execution.HgCommandResultHandler; import org.zmlx.hg4idea.provider.update.HgConflictResolver; import org.zmlx.hg4idea.ui.HgPullDialog; @@ -57,23 +60,29 @@ public class HgMqRebaseAction extends HgAbstractGlobalAction { pullCommand.setSource(dialog.getSource()); pullCommand.setRebase(true); pullCommand.setUpdate(false); - new HgCommandResultNotifier(project).process(pullCommand.execute()); - String currentBranch = new HgTagBranchCommand(project, repository).getCurrentBranch(); - if (StringUtils.isBlank(currentBranch)) { - return; - } + pullCommand.execute(new HgCommandResultHandler() { + @Override + public void process(@Nullable HgCommandResult result) { + new HgCommandResultNotifier(project).process(result); - new HgConflictResolver(project).resolve(repository); + String currentBranch = new HgTagBranchCommand(project, repository).getCurrentBranch(); + if (StringUtils.isBlank(currentBranch)) { + return; + } - HgResolveCommand resolveCommand = new HgResolveCommand(project); - Map status = resolveCommand.list(repository); + new HgConflictResolver(project).resolve(repository); - if (status.containsValue(HgResolveStatusEnum.UNRESOLVED)) { - return; - } + HgResolveCommand resolveCommand = new HgResolveCommand(project); + Map status = resolveCommand.getListSynchronously(repository); - new HgRebaseCommand(project, repository).continueRebase(); + if (status.containsValue(HgResolveStatusEnum.UNRESOLVED)) { + return; + } + + new HgRebaseCommand(project, repository).continueRebase(); + } + }); } }; } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgPullAction.java b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgPullAction.java index 982e6b0451b7..15d705be9c6c 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgPullAction.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgPullAction.java @@ -14,8 +14,10 @@ package org.zmlx.hg4idea.action; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.Nullable; import org.zmlx.hg4idea.command.HgPullCommand; -import org.zmlx.hg4idea.command.HgCommandResult; +import org.zmlx.hg4idea.execution.HgCommandResult; +import org.zmlx.hg4idea.execution.HgCommandResultHandler; import org.zmlx.hg4idea.ui.HgPullDialog; import java.util.Collection; @@ -49,8 +51,12 @@ public class HgPullAction extends HgAbstractGlobalAction { command.setSource(dialog.getSource()); command.setRebase(false); command.setUpdate(false); - HgCommandResult result = command.execute(); - new HgCommandResultNotifier(project).process(result); + command.execute(new HgCommandResultHandler() { + @Override + public void process(@Nullable HgCommandResult result) { + new HgCommandResultNotifier(project).process(result); + } + }); } }; } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgPushAction.java b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgPushAction.java index 3436e733d853..838623bd2177 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgPushAction.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgPushAction.java @@ -14,7 +14,10 @@ package org.zmlx.hg4idea.action; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.Nullable; import org.zmlx.hg4idea.command.HgPushCommand; +import org.zmlx.hg4idea.execution.HgCommandResult; +import org.zmlx.hg4idea.execution.HgCommandResultHandler; import org.zmlx.hg4idea.ui.HgPushDialog; import java.util.Collection; @@ -46,7 +49,12 @@ public class HgPushAction extends HgAbstractGlobalAction { command.setRevision(dialog.getRevision()); command.setForce(dialog.isForce()); command.setBranch(dialog.getBranch()); - new HgCommandResultNotifier(project).process(command.execute()); + command.execute(new HgCommandResultHandler() { + @Override + public void process(@Nullable HgCommandResult result) { + new HgCommandResultNotifier(project).process(result); + } + }); } }; } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgRunConflictResolverAction.java b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgRunConflictResolverAction.java index 71d5bd095f61..7e6802a7c6a6 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgRunConflictResolverAction.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgRunConflictResolverAction.java @@ -12,8 +12,11 @@ // limitations under the License. package org.zmlx.hg4idea.action; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; import org.zmlx.hg4idea.provider.update.HgConflictResolver; import org.zmlx.hg4idea.ui.HgRunConflictResolverDialog; @@ -60,7 +63,12 @@ public class HgRunConflictResolverAction extends HgAbstractGlobalAction { } public void execute() { - new HgConflictResolver(project).resolve(repository); + new Task.Modal(project, "Mercurial resolves conflicts", false) { + @Override + public void run(@NotNull ProgressIndicator indicator) { + new HgConflictResolver(project).resolve(repository); + } + }.queue(); } }; } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgSwitchWorkingDirectoryAction.java b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgSwitchWorkingDirectoryAction.java index 384194da9202..d9d18ff94490 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgSwitchWorkingDirectoryAction.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/action/HgSwitchWorkingDirectoryAction.java @@ -12,11 +12,12 @@ // limitations under the License. package org.zmlx.hg4idea.action; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import org.zmlx.hg4idea.HgVcs; import org.zmlx.hg4idea.command.HgUpdateCommand; -import org.zmlx.hg4idea.command.HgCommandResult; +import org.zmlx.hg4idea.execution.HgCommandResult; import org.zmlx.hg4idea.ui.HgSwitchDialog; import java.util.Collection; @@ -44,7 +45,7 @@ public class HgSwitchWorkingDirectoryAction extends HgAbstractGlobalAction { } public void execute() { - HgUpdateCommand command = new HgUpdateCommand(project, dialog.getRepository()); + final HgUpdateCommand command = new HgUpdateCommand(project, dialog.getRepository()); command.setClean(dialog.isRemoveLocalChanges()); if (dialog.isRevisionSelected()) { command.setRevision(dialog.getRevision()); @@ -56,10 +57,14 @@ public class HgSwitchWorkingDirectoryAction extends HgAbstractGlobalAction { command.setRevision(dialog.getTag().getName()); } - HgCommandResult result = command.execute(); - new HgCommandResultNotifier(project).process(result); - - project.getMessageBus().syncPublisher(HgVcs.BRANCH_TOPIC).update(project); + ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { + @Override + public void run() { + HgCommandResult result = command.execute(); + new HgCommandResultNotifier(project).process(result); + project.getMessageBus().syncPublisher(HgVcs.BRANCH_TOPIC).update(project); + } + }); } }; } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgAddCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgAddCommand.java index 680c7f129c87..7025c7f68227 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgAddCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgAddCommand.java @@ -17,6 +17,7 @@ import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NotNull; import org.zmlx.hg4idea.HgFile; import org.zmlx.hg4idea.HgUtil; +import org.zmlx.hg4idea.execution.HgCommandExecutor; import java.util.Arrays; import java.util.Collection; @@ -48,7 +49,7 @@ public class HgAddCommand { */ public void execute(@NotNull Collection hgFiles) { for(Map.Entry> entry : HgUtil.getRelativePathsByRepository(hgFiles).entrySet()) { - HgCommandService.getInstance(myProject).execute(entry.getKey(), "add", entry.getValue()); + new HgCommandExecutor(myProject).executeInCurrentThread(entry.getKey(), "add", entry.getValue()); } } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgAnnotateCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgAnnotateCommand.java index 07d2c81835b5..257367f385f3 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgAnnotateCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgAnnotateCommand.java @@ -15,6 +15,8 @@ package org.zmlx.hg4idea.command; import com.intellij.openapi.project.Project; import org.zmlx.hg4idea.HgFile; import org.zmlx.hg4idea.HgRevisionNumber; +import org.zmlx.hg4idea.execution.HgCommandResult; +import org.zmlx.hg4idea.execution.HgCommandExecutor; import org.zmlx.hg4idea.provider.annotate.HgAnnotationLine; import org.jetbrains.annotations.NotNull; @@ -44,10 +46,8 @@ public class HgAnnotateCommand { } public List execute(@NotNull HgFile hgFile) { - HgCommandService service = HgCommandService.getInstance(project); - HgCommandResult result = service.execute( - hgFile.getRepo(), "annotate", Arrays.asList("-cqnudl", hgFile.getRelativePath()) - ); + HgCommandExecutor executor = new HgCommandExecutor(project); + HgCommandResult result = executor.executeInCurrentThread(hgFile.getRepo(), "annotate", Arrays.asList("-cqnudl", hgFile.getRelativePath())); List annotations = new ArrayList(); for (String line : result.getOutputLines()) { diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCatCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCatCommand.java index 6beb6ef187d4..6b02f52a3d1c 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCatCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCatCommand.java @@ -17,6 +17,8 @@ import org.apache.commons.lang.StringUtils; import org.jetbrains.annotations.Nullable; import org.zmlx.hg4idea.HgFile; import org.zmlx.hg4idea.HgRevisionNumber; +import org.zmlx.hg4idea.execution.HgCommandExecutor; +import org.zmlx.hg4idea.execution.HgCommandResult; import java.nio.charset.Charset; import java.util.Collections; @@ -34,8 +36,10 @@ public class HgCatCommand { @Nullable public String execute(HgFile hgFile, HgRevisionNumber vcsRevisionNumber, Charset charset) { final List arguments = createArguments(vcsRevisionNumber, hgFile.getRelativePath()); - final HgCommandService service = HgCommandService.getInstance(myProject); - final HgCommandResult result = service.execute(hgFile.getRepo(), Collections.emptyList(), "cat", arguments, charset, true); + final HgCommandExecutor executor = new HgCommandExecutor(myProject); + executor.setOptions(Collections.emptyList()); + executor.setSilent(true); + final HgCommandResult result = executor.executeInCurrentThread(hgFile.getRepo(), "cat", arguments); if (result == null) { // in case of error return null; diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgChangesetsCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgChangesetsCommand.java index 266d457f085c..261fdda52a77 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgChangesetsCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgChangesetsCommand.java @@ -18,8 +18,9 @@ import com.intellij.openapi.vfs.VirtualFile; import org.apache.commons.lang.StringUtils; import org.jetbrains.annotations.Nullable; import org.zmlx.hg4idea.HgRevisionNumber; +import org.zmlx.hg4idea.execution.HgCommandExecutor; +import org.zmlx.hg4idea.execution.HgCommandResult; -import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -83,7 +84,9 @@ public abstract class HgChangesetsCommand { @Nullable protected HgCommandResult executeCommand(VirtualFile repo, List args) { - return HgCommandService.getInstance(project).execute(repo, HgCommandService.DEFAULT_OPTIONS, command, args, Charset.defaultCharset(), isSilentCommand()); + final HgCommandExecutor executor = new HgCommandExecutor(project); + executor.setSilent(isSilentCommand()); + return executor.executeInCurrentThread(repo, command, args); } protected boolean isSilentCommand() { diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCloneCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCloneCommand.java index 1384ddc2f528..16df8fc6235c 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCloneCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCloneCommand.java @@ -2,11 +2,10 @@ package org.zmlx.hg4idea.command; import com.intellij.openapi.project.Project; import org.jetbrains.annotations.Nullable; +import org.zmlx.hg4idea.execution.HgCommandResult; +import org.zmlx.hg4idea.execution.HgCommandExecutor; -import java.nio.charset.Charset; import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; import java.util.List; public class HgCloneCommand { @@ -14,7 +13,6 @@ public class HgCloneCommand { private String repositoryURL; private String directory; - private final HgCommandAuthenticator authenticator = new HgCommandAuthenticator(); public HgCloneCommand(Project project) { this.project = project; @@ -33,6 +31,8 @@ public class HgCloneCommand { final List arguments = new ArrayList(2); arguments.add(repositoryURL); arguments.add(directory); - return authenticator.executeCommandAndAuthenticateIfNecessary(project, null, repositoryURL, "clone", arguments); + final HgCommandExecutor executor = new HgCommandExecutor(project); + executor.setShowOutput(true); + return executor.executeInCurrentThread(null, "clone", arguments); } } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCommandAuthenticator.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCommandAuthenticator.java deleted file mode 100644 index 5007815066f6..000000000000 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCommandAuthenticator.java +++ /dev/null @@ -1,258 +0,0 @@ -// Copyright 2008-2010 Victor Iacoban -// -// 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.zmlx.hg4idea.command; - -import com.intellij.ide.passwordSafe.PasswordSafe; -import com.intellij.ide.passwordSafe.PasswordSafeException; -import com.intellij.ide.passwordSafe.config.PasswordSafeSettings; -import com.intellij.ide.passwordSafe.impl.PasswordSafeImpl; -import com.intellij.ide.passwordSafe.impl.PasswordSafeProvider; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.util.ui.UIUtil; -import com.intellij.vcsUtil.VcsUtil; -import org.apache.commons.lang.StringUtils; -import org.jetbrains.annotations.Nullable; -import org.zmlx.hg4idea.HgGlobalSettings; -import org.zmlx.hg4idea.HgUtil; -import org.zmlx.hg4idea.HgVcs; -import org.zmlx.hg4idea.ui.HgUsernamePasswordDialog; - -import java.net.URISyntaxException; -import java.util.List; -import java.util.Map; - -import static org.zmlx.hg4idea.command.HgErrorUtil.isAbort; -import static org.zmlx.hg4idea.command.HgErrorUtil.isAuthorizationError; - -/** - * Base class for any command interacting with a remote repository and which needs authentication. - */ -class HgCommandAuthenticator { - - private static final Logger LOG = Logger.getInstance(HgCommandAuthenticator.class.getName()); - - @Nullable - protected HgCommandResult executeCommandAndAuthenticateIfNecessary(Project project, VirtualFile localRepository, String remoteRepository, String command, List arguments) { - return executeCommandAndAuthenticateIfNecessary(project, localRepository, remoteRepository, command, arguments, 0); - } - /** - * Tries to execute an hg command and analyzes the output. - * If authentication is needed, watches to the PasswordSafe for the saved password. Otherwise asks the user. - * Repeats it 3 times to give a chance to retry. - * @param project - * @param localRepository - * @param remoteRepository - * @param command - * @param arguments - * @param urlPosition - * @return - */ - @Nullable - protected HgCommandResult executeCommandAndAuthenticateIfNecessary(Project project, VirtualFile localRepository, String remoteRepository, String command, List arguments, int urlArgumentPosition) { - HgCommandResult result = HgCommandService.getInstance(project).execute(localRepository, command, arguments); // try to execute without authentication data - if (isAuthorizationError(result)) { - try { - // get auth data from password safe of from user and inject to the url - HgUrl hgUrl = new HgUrl(remoteRepository); - if (hgUrl.supportsAuthentication()) { - final GetPasswordRunnable runnable = new GetPasswordRunnable(project, hgUrl); - for (int i = 0; i < 3; i++) { - if (i == 1) { - runnable.setForceShowDialog(true); // first time try to get info from password safe if it's there, next time don't even try, - // because it means that the saved data didn't pass the authentication - } - result = tryToAuthenticate(project, localRepository, hgUrl, runnable, command, arguments, urlArgumentPosition); - if (result == HgCommandResult.CANCELLED) { - return result; - } - if (isAbort(result)) { - if (isAuthorizationError(result)) { - continue; - } else { - return result; - } - } - saveCredentials(project, runnable); - return result; - } - HgUtil.notifyError(project, "Authentication failed", "Authentication to " + remoteRepository + " failed"); - return result; - } else { - HgUtil.notifyError(project, "Authentication error", "Authentication was requested, but " + hgUrl.getScheme() + " doesn't support it."); - } - } catch (URISyntaxException e) { - VcsUtil.showErrorMessage(project, "Invalid repository: " + remoteRepository, "Error"); - } - } - return result; - } - - /** - * Shows the auth dialog if needed, fills authentication data to the given hgurl and returns the result of command execution. - * NB: hgUrl is modified - */ - @Nullable - private static HgCommandResult tryToAuthenticate(Project project, VirtualFile localRepository, HgUrl hgUrl, GetPasswordRunnable runnable, String command, List arguments, int urlArgumentPosition) throws URISyntaxException { - HgCommandService service = HgCommandService.getInstance(project); - - UIUtil.invokeAndWaitIfNeeded(runnable); - if (runnable.isOk()) { - hgUrl.setUsername(runnable.getUserName()); - hgUrl.setPassword(String.valueOf(runnable.getPassword())); - } else { - return HgCommandResult.CANCELLED; - } - - arguments.set(urlArgumentPosition, hgUrl.asString()); - return service.execute(localRepository, command, arguments); - } - - private static void saveCredentials(Project project, GetPasswordRunnable runnable) { - final PasswordSafeImpl passwordSafe = (PasswordSafeImpl)PasswordSafe.getInstance(); - if (passwordSafe.getSettings().getProviderType().equals(PasswordSafeSettings.ProviderType.DO_NOT_STORE)) { - return; - } - final String key = keyForUrlAndLogin(runnable.getURL(), runnable.getUserName()); - - final PasswordSafeProvider provider = runnable.isRememberPassword() ? passwordSafe.getMasterKeyProvider() : passwordSafe.getMemoryProvider(); - try { - provider.storePassword(project, HgCommandAuthenticator.class, key, runnable.getPassword()); - final HgVcs vcs = HgVcs.getInstance(project); - if (vcs != null) { - vcs.getGlobalSettings().addRememberedUrl(runnable.getURL(), runnable.getUserName()); - } - } catch (PasswordSafeException e) { - LOG.info("Couldn't store the password for key [" + key + "]", e); - } - } - - private static class GetPasswordRunnable implements Runnable { - - private final HgUrl hgUrl; - private String userName; - private String myPassword; - private Project project; - private boolean myForceShowDialog; - private boolean ok = false; - private static final Logger LOG = Logger.getInstance(GetPasswordRunnable.class.getName()); - private String myURL; - private boolean myRememberPassword; - - public GetPasswordRunnable(Project project, HgUrl hgUrl) { - this.hgUrl = hgUrl; - this.project = project; - } - - public void run() { - - // get the string representation of the url - @Nullable String stringUrl = null; - try { - stringUrl = hgUrl.asString(); - } - catch (URISyntaxException e) { - LOG.warn("Couldn't parse hgUrl: [" + hgUrl + "]", e); - } - - // find if we've already been here - final HgVcs vcs = HgVcs.getInstance(project); - if (vcs == null) { return; } - - final HgGlobalSettings hgGlobalSettings = vcs.getGlobalSettings(); - final Map> urls = hgGlobalSettings.getRememberedUrls(); - @Nullable List rememberedLoginsForUrl = urls.get(stringUrl); - - String login = hgUrl.getUsername(); - if (StringUtils.isBlank(login)) { - // find the last used login - if (rememberedLoginsForUrl != null && !rememberedLoginsForUrl.isEmpty()) { - login = rememberedLoginsForUrl.get(0); - } - } - - String password = hgUrl.getPassword(); - if (StringUtils.isBlank(password) && stringUrl != null) { - // if we've logged in with this login, search for password - final String key = keyForUrlAndLogin(stringUrl, login); - try { - final PasswordSafeImpl passwordSafe = (PasswordSafeImpl)PasswordSafe.getInstance(); - password = passwordSafe.getMemoryProvider().getPassword(project, HgCommandAuthenticator.class, key); - if (password == null && passwordSafe.getSettings().getProviderType().equals(PasswordSafeSettings.ProviderType.MASTER_PASSWORD)) { - password = passwordSafe.getMasterKeyProvider().getPassword(project, HgCommandAuthenticator.class, key); - } - } catch (PasswordSafeException e) { - LOG.info("Couldn't get password for key [" + key + "]", e); - } - } - - // don't show dialog if we can (not forced + both fields are known) - if (!myForceShowDialog && !StringUtils.isBlank(password) && !StringUtils.isBlank(login)) { - userName = login; - myPassword = password; - ok = true; - return; - } - - String url; - try { - url = hgUrl.asString(false); - } - catch (URISyntaxException e) { - url = null; - } - final HgUsernamePasswordDialog dialog = new HgUsernamePasswordDialog(project, url, login, password); - dialog.show(); - if (dialog.isOK()) { - userName = dialog.getUsername(); - myPassword = dialog.getPassword(); - ok = true; - - myRememberPassword = dialog.isRememberPassword(); - if (stringUrl != null) { - myURL = stringUrl; - } - } - } - - public String getUserName() { - return userName; - } - - public String getPassword() { - return myPassword; - } - - public boolean isOk() { - return ok; - } - - public String getURL() { - return myURL; - } - - public void setForceShowDialog(boolean forceShowDialog) { - myForceShowDialog = forceShowDialog; - } - - public boolean isRememberPassword() { - return myRememberPassword; - } - } - - private static String keyForUrlAndLogin(String stringUrl, String login) { - return stringUrl + login; - } - -} diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCommitCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCommitCommand.java index 07a3170242e8..c4ac4d2e3ea6 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCommitCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCommitCommand.java @@ -22,6 +22,8 @@ import org.jetbrains.annotations.NotNull; import org.zmlx.hg4idea.HgFile; import org.zmlx.hg4idea.HgVcs; import org.zmlx.hg4idea.HgVcsMessages; +import org.zmlx.hg4idea.execution.HgCommandException; +import org.zmlx.hg4idea.execution.HgCommandExecutor; import java.io.BufferedWriter; import java.io.File; @@ -68,7 +70,7 @@ public class HgCommitCommand { for (HgFile hgFile : files) { parameters.add(hgFile.getRelativePath()); } - ensureSuccess(HgCommandService.getInstance(project).execute(repo, "commit", parameters)); + ensureSuccess(new HgCommandExecutor(project).executeInCurrentThread(repo, "commit", parameters)); project.getMessageBus().syncPublisher(HgVcs.REMOTE_TOPIC).update(project); } catch (IOException e) { LOG.info(e); diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCopyCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCopyCommand.java index a99f63d1f073..7b2ed5b9c4a0 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCopyCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCopyCommand.java @@ -14,6 +14,7 @@ package org.zmlx.hg4idea.command; import com.intellij.openapi.project.Project; import org.zmlx.hg4idea.HgFile; +import org.zmlx.hg4idea.execution.HgCommandExecutor; import java.util.Arrays; @@ -26,10 +27,9 @@ public class HgCopyCommand { } public void execute(HgFile source, HgFile target) { - HgCommandService service = HgCommandService.getInstance(project); + HgCommandExecutor executor = new HgCommandExecutor(project); if (source.getRepo().equals(target.getRepo())) { - service.execute(source.getRepo(), "copy", - Arrays.asList("--after", source.getRelativePath(), target.getRelativePath())); + executor.execute(source.getRepo(), "copy", Arrays.asList("--after", source.getRelativePath(), target.getRelativePath()), null); } } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgIdentifyCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgIdentifyCommand.java index dbc3d899cf79..fe65c06bd869 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgIdentifyCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgIdentifyCommand.java @@ -2,17 +2,15 @@ package org.zmlx.hg4idea.command; import com.intellij.openapi.project.Project; import org.jetbrains.annotations.Nullable; +import org.zmlx.hg4idea.execution.HgCommandResult; +import org.zmlx.hg4idea.execution.HgCommandExecutor; -import java.nio.charset.Charset; -import java.util.Collection; -import java.util.Collections; import java.util.LinkedList; import java.util.List; public class HgIdentifyCommand { private final Project project; - private final HgCommandAuthenticator authenticator = new HgCommandAuthenticator(); private String source; public HgIdentifyCommand(Project project) { @@ -31,6 +29,8 @@ public class HgIdentifyCommand { public HgCommandResult execute() { final List arguments = new LinkedList(); arguments.add(source); - return authenticator.executeCommandAndAuthenticateIfNecessary(project, null, source, "identify", arguments); + final HgCommandExecutor executor = new HgCommandExecutor(project); + executor.setSilent(true); + return executor.executeInCurrentThread(null, "identify", arguments); } } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgInitCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgInitCommand.java index 670ba67983c1..9043571410b0 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgInitCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgInitCommand.java @@ -2,7 +2,13 @@ package org.zmlx.hg4idea.command; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.Consumer; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.zmlx.hg4idea.HgErrorUtil; +import org.zmlx.hg4idea.execution.HgCommandExecutor; +import org.zmlx.hg4idea.execution.HgCommandResult; +import org.zmlx.hg4idea.execution.HgCommandResultHandler; import java.util.ArrayList; import java.util.List; @@ -18,11 +24,17 @@ public class HgInitCommand { myProject = project; } - public boolean execute(@NotNull VirtualFile repositoryRoot) { + public void execute(@NotNull VirtualFile repositoryRoot, final Consumer booleanResultHandler) { final List args = new ArrayList(1); args.add(repositoryRoot.getPath()); - final HgCommandResult result = HgCommandService.getInstance(myProject).execute(null, "init", args); - return result != null && !HgErrorUtil.isAbort(result); + final HgCommandExecutor executor = new HgCommandExecutor(myProject); + executor.setShowOutput(true); + executor.execute(null, "init", args, new HgCommandResultHandler() { + @Override + public void process(@Nullable HgCommandResult result) { + booleanResultHandler.consume(result != null && !HgErrorUtil.isAbort(result)); + } + }); } } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgLogCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgLogCommand.java index e69c9bc122ed..2a861b3dba50 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgLogCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgLogCommand.java @@ -15,6 +15,8 @@ package org.zmlx.hg4idea.command; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import org.zmlx.hg4idea.HgFile; +import org.zmlx.hg4idea.execution.HgCommandExecutor; +import org.zmlx.hg4idea.execution.HgCommandResult; import java.util.LinkedList; import java.util.List; @@ -41,7 +43,7 @@ public class HgLogCommand extends HgRevisionsCommand { } @Override - public HgCommandResult execute(HgCommandService service, VirtualFile repo, + public HgCommandResult execute(HgCommandExecutor executor, VirtualFile repo, String template, int limit, HgFile hgFile) { List arguments = new LinkedList(); if (followCopies) { @@ -64,7 +66,7 @@ public class HgLogCommand extends HgRevisionsCommand { if (logFile) { arguments.add(hgFile.getRelativePath()); } - return service.execute(repo, "log", arguments); + return executor.executeInCurrentThread(repo, "log", arguments); } } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgMQCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgMQCommand.java index d279f776ee0d..e871b18c3edd 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgMQCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgMQCommand.java @@ -15,6 +15,7 @@ package org.zmlx.hg4idea.command; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NotNull; +import org.zmlx.hg4idea.execution.HgCommandExecutor; import java.util.List; @@ -27,7 +28,7 @@ public class HgMQCommand { } public List qapplied(@NotNull VirtualFile repo) { - return HgCommandService.getInstance(project).execute(repo, "qapplied", null).getOutputLines(); + return new HgCommandExecutor(project).executeInCurrentThread(repo, "qapplied", null).getOutputLines(); } } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgMergeCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgMergeCommand.java index 99d50cb64258..600c8be8c4a3 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgMergeCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgMergeCommand.java @@ -15,6 +15,9 @@ package org.zmlx.hg4idea.command; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import org.apache.commons.lang.StringUtils; +import org.jetbrains.annotations.Nullable; +import org.zmlx.hg4idea.execution.HgCommandExecutor; +import org.zmlx.hg4idea.execution.HgCommandResult; import java.util.LinkedList; import java.util.List; @@ -40,8 +43,10 @@ public class HgMergeCommand { this.revision = revision; } + @Nullable public HgCommandResult execute() { - HgCommandService commandService = HgCommandService.getInstance(project); + HgCommandExecutor commandExecutor = new HgCommandExecutor(project); + commandExecutor.setShowOutput(true); List arguments = new LinkedList(); if (StringUtils.isNotBlank(revision)) { arguments.add("--rev"); @@ -49,7 +54,7 @@ public class HgMergeCommand { } else if (StringUtils.isNotBlank(branch)) { arguments.add(branch); } - return commandService.execute(repo, "merge", arguments); + return commandExecutor.executeInCurrentThread(repo, "merge", arguments); } } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgMoveCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgMoveCommand.java index c764ac4c4674..6516a6084c81 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgMoveCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgMoveCommand.java @@ -14,6 +14,7 @@ package org.zmlx.hg4idea.command; import com.intellij.openapi.project.Project; import org.zmlx.hg4idea.HgFile; +import org.zmlx.hg4idea.execution.HgCommandExecutor; import java.util.Arrays; @@ -27,7 +28,7 @@ public class HgMoveCommand { public void execute(HgFile source, HgFile target) { if (source.getRepo().equals(target.getRepo())) { - HgCommandService.getInstance(project).execute(source.getRepo(), "rename", + new HgCommandExecutor(project).executeInCurrentThread(source.getRepo(), "rename", Arrays.asList("--after", source.getRelativePath(), target.getRelativePath())); } } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgParentsCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgParentsCommand.java index cc3a0339b901..d072540feb84 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgParentsCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgParentsCommand.java @@ -36,4 +36,9 @@ public class HgParentsCommand extends HgChangesetsCommand{ args.add(revision.getChangeset()); } } + + @Override + protected boolean isSilentCommand() { + return true; + } } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgPullCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgPullCommand.java index c5d1a3fb1287..c3843396948b 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgPullCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgPullCommand.java @@ -16,7 +16,11 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import org.apache.commons.lang.StringUtils; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.zmlx.hg4idea.HgVcs; +import org.zmlx.hg4idea.execution.HgCommandExecutor; +import org.zmlx.hg4idea.execution.HgCommandResult; +import org.zmlx.hg4idea.execution.HgCommandResultHandler; import java.util.LinkedList; import java.util.List; @@ -31,8 +35,6 @@ public class HgPullCommand { private boolean update = true; private boolean rebase = !update; - private final HgCommandAuthenticator authenticator = new HgCommandAuthenticator(); - public HgPullCommand(Project project, @NotNull VirtualFile repo) { this.project = project; this.repo = repo; @@ -54,7 +56,7 @@ public class HgPullCommand { this.source = source; } - public HgCommandResult execute() { + public void execute(@Nullable final HgCommandResultHandler resultHandler) { List arguments = new LinkedList(); if (update) { arguments.add("--update"); @@ -69,11 +71,17 @@ public class HgPullCommand { arguments.add(source); - HgCommandResult result = authenticator.executeCommandAndAuthenticateIfNecessary(project, repo, source, "pull", arguments, arguments.size()-1); - - project.getMessageBus().syncPublisher(HgVcs.REMOTE_TOPIC).update(project); - - return result; + final HgCommandExecutor executor = new HgCommandExecutor(project); + executor.setShowOutput(true); + executor.execute(repo, "pull", arguments, new HgCommandResultHandler() { + @Override + public void process(@Nullable HgCommandResult result) { + project.getMessageBus().syncPublisher(HgVcs.REMOTE_TOPIC).update(project); + if (resultHandler != null) { + resultHandler.process(result); + } + } + }); } } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgPushCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgPushCommand.java index 8a80c2cbc925..23ad409064a0 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgPushCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgPushCommand.java @@ -16,7 +16,11 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import org.apache.commons.lang.StringUtils; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.zmlx.hg4idea.HgVcs; +import org.zmlx.hg4idea.execution.HgCommandExecutor; +import org.zmlx.hg4idea.execution.HgCommandResult; +import org.zmlx.hg4idea.execution.HgCommandResultHandler; import java.util.LinkedList; import java.util.List; @@ -26,7 +30,6 @@ public class HgPushCommand { private final Project myProject; private final VirtualFile myRepo; private final String myDestination; - private final HgCommandAuthenticator authenticator = new HgCommandAuthenticator(); private String myRevision; private boolean myForce; @@ -50,7 +53,7 @@ public class HgPushCommand { myBranch = branch; } - public HgCommandResult execute() { + public void execute(final HgCommandResultHandler resultHandler) { final List arguments = new LinkedList(); if (StringUtils.isNotBlank(myRevision)) { arguments.add("-r"); @@ -65,9 +68,15 @@ public class HgPushCommand { } arguments.add(myDestination); - final HgCommandResult result = authenticator.executeCommandAndAuthenticateIfNecessary(myProject, myRepo, myDestination, "push", arguments, arguments.size()-1); - myProject.getMessageBus().syncPublisher(HgVcs.REMOTE_TOPIC).update(myProject); - return result; + final HgCommandExecutor executor = new HgCommandExecutor(myProject); + executor.setShowOutput(true); + executor.execute(myRepo, "push", arguments, new HgCommandResultHandler() { + @Override + public void process(@Nullable HgCommandResult result) { + myProject.getMessageBus().syncPublisher(HgVcs.REMOTE_TOPIC).update(myProject); + resultHandler.process(result); + } + }); } } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgRebaseCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgRebaseCommand.java index 1f50ea1a9f4b..b91555d8a72f 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgRebaseCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgRebaseCommand.java @@ -15,6 +15,7 @@ package org.zmlx.hg4idea.command; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NotNull; +import org.zmlx.hg4idea.execution.HgCommandExecutor; import java.util.Arrays; @@ -29,7 +30,7 @@ public class HgRebaseCommand { } public void continueRebase() { - HgCommandService.getInstance(project).execute(repo, "rebase", Arrays.asList("--continue")); + new HgCommandExecutor(project).execute(repo, "rebase", Arrays.asList("--continue"), null); } } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgRemoteChangesetsCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgRemoteChangesetsCommand.java index 5166952b3166..2dedb57a86cb 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgRemoteChangesetsCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgRemoteChangesetsCommand.java @@ -25,6 +25,8 @@ import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NotNull; import org.zmlx.hg4idea.HgProjectSettings; import org.zmlx.hg4idea.HgVcs; +import org.zmlx.hg4idea.execution.HgCommandExecutor; +import org.zmlx.hg4idea.execution.HgCommandResult; import javax.swing.event.HyperlinkEvent; import java.util.List; @@ -51,7 +53,7 @@ public abstract class HgRemoteChangesetsCommand extends HgChangesetsCommand { @Override protected HgCommandResult executeCommand(VirtualFile repo, List args) { String repositoryURL = new HgShowConfigCommand(project).getDefaultPath(repo); - HgCommandResult result = new HgCommandAuthenticator().executeCommandAndAuthenticateIfNecessary(project, repo, repositoryURL, command, args, args.size()-1); + HgCommandResult result = new HgCommandExecutor(project).executeInCurrentThread(repo, command, args); if (result == HgCommandResult.CANCELLED) { final HgVcs vcs = HgVcs.getInstance(project); Notifications.Bus.notify(new Notification(HgVcs.NOTIFICATION_GROUP_ID, "Checking for incoming/outgoing changes disabled", diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgRemoveCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgRemoveCommand.java index 9e08ac08a3aa..6f21cff6bcbb 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgRemoveCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgRemoveCommand.java @@ -17,6 +17,7 @@ import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NotNull; import org.zmlx.hg4idea.HgFile; import org.zmlx.hg4idea.HgUtil; +import org.zmlx.hg4idea.execution.HgCommandExecutor; import java.util.Arrays; import java.util.Collection; @@ -50,7 +51,7 @@ public class HgRemoveCommand { for( Map.Entry> entry : HgUtil.getRelativePathsByRepository(hgFiles).entrySet()) { List filePaths = entry.getValue(); filePaths.add(0, "--after"); - HgCommandService.getInstance(myProject).execute(entry.getKey(), "remove", filePaths); + new HgCommandExecutor(myProject).executeInCurrentThread(entry.getKey(), "remove", filePaths); } } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgResolveCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgResolveCommand.java index eb2e7e09e6b0..e38814773a1f 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgResolveCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgResolveCommand.java @@ -15,8 +15,13 @@ package org.zmlx.hg4idea.command; import com.intellij.openapi.project.Project; import com.intellij.openapi.vcs.FilePath; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.Consumer; import org.apache.commons.lang.StringUtils; +import org.jetbrains.annotations.Nullable; import org.zmlx.hg4idea.HgFile; +import org.zmlx.hg4idea.execution.HgCommandExecutor; +import org.zmlx.hg4idea.execution.HgCommandResult; +import org.zmlx.hg4idea.execution.HgCommandResultHandler; import java.io.File; import java.util.Arrays; @@ -34,15 +39,40 @@ public class HgResolveCommand { myProject = project; } - public Map list(VirtualFile repo) { + + public Map getListSynchronously(VirtualFile repo) { if (repo == null) { return Collections.emptyMap(); } - final HgCommandResult result = HgCommandService.getInstance(myProject).execute(repo, "resolve", Arrays.asList("--list")); + final HgCommandExecutor executor = new HgCommandExecutor(myProject); + executor.setSilent(true); + final HgCommandResult result = executor.executeInCurrentThread(repo, "resolve", Arrays.asList("--list")); if (result == null) { return Collections.emptyMap(); } + return handleResult(repo, result); + } + public void list(final VirtualFile repo, final Consumer> resultHandler) { + if (repo == null) { + resultHandler.consume(Collections.emptyMap()); + } + final HgCommandExecutor executor = new HgCommandExecutor(myProject); + executor.setSilent(true); + executor.execute(repo, "resolve", Arrays.asList("--list"), new HgCommandResultHandler() { + @Override + public void process(@Nullable HgCommandResult result) { + if (result == null) { + resultHandler.consume(Collections.emptyMap()); + } + + final Map resolveStatus = handleResult(repo, result); + resultHandler.consume(resolveStatus); + } + }); + } + + private static Map handleResult(VirtualFile repo, HgCommandResult result) { final Map resolveStatus = new HashMap(); for (String line : result.getOutputLines()) { if (StringUtils.isBlank(line) || line.length() < ITEM_COUNT) { @@ -58,11 +88,11 @@ public class HgResolveCommand { } public void markResolved(VirtualFile repo, VirtualFile path) { - HgCommandService.getInstance(myProject).execute(repo, "resolve", Arrays.asList("--mark", path.getPath())); + new HgCommandExecutor(myProject).execute(repo, "resolve", Arrays.asList("--mark", path.getPath()), null); } public void markResolved(VirtualFile repo, FilePath path) { - HgCommandService.getInstance(myProject).execute(repo, "resolve", Arrays.asList("--mark", path.getPath())); + new HgCommandExecutor(myProject).execute(repo, "resolve", Arrays.asList("--mark", path.getPath()), null); } } \ No newline at end of file diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgRevertCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgRevertCommand.java index 966f8faf9a77..d81e13afd3c1 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgRevertCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgRevertCommand.java @@ -17,6 +17,7 @@ import com.intellij.util.containers.ContainerUtil; import org.apache.commons.lang.StringUtils; import org.zmlx.hg4idea.HgFile; import org.zmlx.hg4idea.HgRevisionNumber; +import org.zmlx.hg4idea.execution.HgCommandExecutor; import java.util.LinkedList; import java.util.List; @@ -48,6 +49,6 @@ public class HgRevertCommand { ContainerUtil.addAll(arguments, hgFile.getRelativePath()); - HgCommandService.getInstance(project).execute(hgFile.getRepo(), "revert", arguments); + new HgCommandExecutor(project).execute(hgFile.getRepo(), "revert", arguments, null); } } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgRevisionsCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgRevisionsCommand.java index 31fe93021c30..3e5ee5319ce6 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgRevisionsCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgRevisionsCommand.java @@ -23,6 +23,8 @@ import org.zmlx.hg4idea.HgFile; import org.zmlx.hg4idea.HgFileRevision; import org.zmlx.hg4idea.HgRevisionNumber; import org.zmlx.hg4idea.HgUtil; +import org.zmlx.hg4idea.execution.HgCommandResult; +import org.zmlx.hg4idea.execution.HgCommandExecutor; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -61,7 +63,7 @@ abstract class HgRevisionsCommand { @Nullable protected abstract HgCommandResult execute( - HgCommandService service, VirtualFile repo, String template, int limit, HgFile hgFile + HgCommandExecutor executor, VirtualFile repo, String template, int limit, HgFile hgFile ); public final List execute(HgFile hgFile, int limit, boolean includeFiles) { @@ -69,7 +71,7 @@ abstract class HgRevisionsCommand { return Collections.emptyList(); } - HgCommandService hgCommandService = HgCommandService.getInstance(project); + HgCommandExecutor hgCommandExecutor = new HgCommandExecutor(project); String template = includeFiles ? LONG_TEMPLATE : SHORT_TEMPLATE; int itemCount = includeFiles ? LONG_ITEM_COUNT : SHORT_ITEM_COUNT; @@ -77,7 +79,7 @@ abstract class HgRevisionsCommand { FilePath originalFileName = HgUtil.getOriginalFileName(hgFile.toFilePath(), ChangeListManager.getInstance(project)); HgFile originalHgFile = new HgFile(hgFile.getRepo(), originalFileName); HgCommandResult result = execute( - hgCommandService, hgFile.getRepo(), template, limit, originalHgFile + hgCommandExecutor, hgFile.getRepo(), template, limit, originalHgFile ); List revisions = new LinkedList(); diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgShowConfigCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgShowConfigCommand.java index 726708f20d3e..2f668bdc334f 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgShowConfigCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgShowConfigCommand.java @@ -15,8 +15,9 @@ package org.zmlx.hg4idea.command; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import org.apache.commons.lang.StringUtils; +import org.zmlx.hg4idea.execution.HgCommandExecutor; +import org.zmlx.hg4idea.execution.HgCommandResult; -import java.nio.charset.Charset; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -47,8 +48,9 @@ public class HgShowConfigCommand { return Collections.emptyMap(); } - HgCommandResult result = - HgCommandService.getInstance(project).execute(repo, HgCommandService.DEFAULT_OPTIONS, "showconfig", null, Charset.defaultCharset(), true); + final HgCommandExecutor executor = new HgCommandExecutor(project); + executor.setSilent(true); + HgCommandResult result = executor.executeInCurrentThread(repo, "showconfig", null); if (result == null) { return Collections.emptyMap(); diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgStatusCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgStatusCommand.java index 7c429d2d400e..b4a1cae8ea0a 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgStatusCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgStatusCommand.java @@ -16,13 +16,14 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import org.apache.commons.lang.StringUtils; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import org.zmlx.hg4idea.HgChange; import org.zmlx.hg4idea.HgFile; +import org.zmlx.hg4idea.HgFileStatusEnum; import org.zmlx.hg4idea.HgRevisionNumber; +import org.zmlx.hg4idea.execution.HgCommandExecutor; +import org.zmlx.hg4idea.execution.HgCommandResult; import java.io.File; -import java.nio.charset.Charset; import java.util.*; public class HgStatusCommand { @@ -88,18 +89,12 @@ public class HgStatusCommand { return doExecute(repo, null); } - @Nullable - public HgChange execute(VirtualFile repo, @NotNull String file) { - Set changes = doExecute(repo, file); - return changes.isEmpty() ? null : changes.iterator().next(); - } - private Set doExecute(VirtualFile repo, String file) { if (repo == null) { return Collections.emptySet(); } - HgCommandService service = HgCommandService.getInstance(project); + HgCommandExecutor executor = new HgCommandExecutor(project); List arguments = new LinkedList(); if (includeAdded) { @@ -136,7 +131,8 @@ public class HgStatusCommand { arguments.add(file); } - HgCommandResult result = service.execute(repo, HgCommandService.DEFAULT_OPTIONS, "status", arguments, Charset.defaultCharset(), true); + executor.setSilent(true); + HgCommandResult result = executor.executeInCurrentThread(repo, "status", arguments); Set changes = new HashSet(); HgChange previous = null; if (result == null) { diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgTagBranchCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgTagBranchCommand.java index 236f2a6e2b29..2e514307552d 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgTagBranchCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgTagBranchCommand.java @@ -14,10 +14,14 @@ package org.zmlx.hg4idea.command; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.Consumer; import org.apache.commons.lang.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.zmlx.hg4idea.HgRevisionNumber; +import org.zmlx.hg4idea.execution.HgCommandExecutor; +import org.zmlx.hg4idea.execution.HgCommandResult; +import org.zmlx.hg4idea.execution.HgCommandResultHandler; import java.util.LinkedList; import java.util.List; @@ -41,7 +45,7 @@ public class HgTagBranchCommand { @Nullable public String getCurrentBranch() { - HgCommandResult result = HgCommandService.getInstance(project).execute(repo, "branch", null); + HgCommandResult result = new HgCommandExecutor(project).executeInCurrentThread(repo, "branch", null); if (result == null) { return null; } @@ -52,12 +56,22 @@ public class HgTagBranchCommand { return StringUtils.trim(output.get(0)); } - public List listBranches() { - return tokenize(HgCommandService.getInstance(project).execute(repo, "branches", null)); + public void listBranches(final Consumer> branchListConsumer) { + new HgCommandExecutor(project).execute(repo, "branches", null, new HgCommandResultHandler() { + @Override + public void process(@Nullable HgCommandResult result) { + branchListConsumer.consume(tokenize(result)); + } + }); } - public List listTags() { - return tokenize(HgCommandService.getInstance(project).execute(repo, "tags", null)); + public void listTags(final Consumer> tagListConsumer) { + new HgCommandExecutor(project).execute(repo, "tags", null, new HgCommandResultHandler() { + @Override + public void process(@Nullable HgCommandResult result) { + tagListConsumer.consume(tokenize(result)); + } + }); } private List tokenize(HgCommandResult result) { diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgTagCreateCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgTagCreateCommand.java index 46f10263a864..66f1c20f011b 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgTagCreateCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgTagCreateCommand.java @@ -16,6 +16,9 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import org.apache.commons.lang.StringUtils; import org.jetbrains.annotations.NotNull; +import org.zmlx.hg4idea.execution.HgCommandException; +import org.zmlx.hg4idea.execution.HgCommandExecutor; +import org.zmlx.hg4idea.execution.HgCommandResultHandler; import java.util.Arrays; @@ -31,11 +34,11 @@ public class HgTagCreateCommand { this.tagName = tagName; } - public HgCommandResult execute() throws HgCommandException { + public void execute(HgCommandResultHandler resultHandler) throws HgCommandException { if (StringUtils.isBlank(tagName)) { throw new HgCommandException("tag name is empty"); } - return HgCommandService.getInstance(project).execute(repo, "tag", Arrays.asList(tagName)); + new HgCommandExecutor(project).execute(repo, "tag", Arrays.asList(tagName), resultHandler); } } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgTrackFileNamesAccrossRevisionsCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgTrackFileNamesAccrossRevisionsCommand.java index 714ca336b3e9..ee141ca08e9a 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgTrackFileNamesAccrossRevisionsCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgTrackFileNamesAccrossRevisionsCommand.java @@ -17,6 +17,8 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import org.apache.commons.lang.StringUtils; import org.zmlx.hg4idea.HgFile; +import org.zmlx.hg4idea.execution.HgCommandExecutor; +import org.zmlx.hg4idea.execution.HgCommandResult; import java.util.*; @@ -38,7 +40,7 @@ class HgTrackFileNamesAccrossRevisionsCommand { this.project = project; } - private HgCommandResult execute(HgCommandService service, VirtualFile repo, int limit, HgFile hgFile, String currentrevision, String givenRevision) { + private HgCommandResult execute(HgCommandExecutor executor, VirtualFile repo, int limit, HgFile hgFile, String currentrevision, String givenRevision) { List arguments = new LinkedList(); arguments.add("--rev"); @@ -55,7 +57,7 @@ class HgTrackFileNamesAccrossRevisionsCommand { arguments.add(hgFile.getRelativePath()); - return service.execute(repo, "log", arguments); + return executor.executeInCurrentThread(repo, "log", arguments); } public final String execute(HgFile hgFile, String currentRevision, String givenRevision, int limit) { @@ -63,8 +65,8 @@ class HgTrackFileNamesAccrossRevisionsCommand { return null; } - HgCommandService hgCommandService = HgCommandService.getInstance(project); - HgCommandResult result = execute(hgCommandService, hgFile.getRepo(), limit, hgFile, currentRevision, givenRevision); + HgCommandExecutor hgCommandExecutor = new HgCommandExecutor(project); + HgCommandResult result = execute(hgCommandExecutor, hgFile.getRepo(), limit, hgFile, currentRevision, givenRevision); String output = result.getRawOutput(); String[] changeSets = output.split(SEPARATOR_STRING); diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgUpdateCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgUpdateCommand.java index 730f9fc6eecc..b4f6c28ab145 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgUpdateCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgUpdateCommand.java @@ -16,6 +16,9 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import org.apache.commons.lang.StringUtils; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.zmlx.hg4idea.execution.HgCommandExecutor; +import org.zmlx.hg4idea.execution.HgCommandResult; import java.util.LinkedList; import java.util.List; @@ -46,6 +49,7 @@ public class HgUpdateCommand { this.clean = clean; } + @Nullable public HgCommandResult execute() { List arguments = new LinkedList(); if (clean) { @@ -59,7 +63,9 @@ public class HgUpdateCommand { arguments.add(branch); } - return HgCommandService.getInstance(project).execute(repo, "update", arguments); + final HgCommandExecutor executor = new HgCommandExecutor(project); + executor.setShowOutput(true); + return executor.executeInCurrentThread(repo, "update", arguments); } } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgUrl.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgUrl.java deleted file mode 100644 index 0b6bdf1747f5..000000000000 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgUrl.java +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright 2008-2010 Victor Iacoban -// -// 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.zmlx.hg4idea.command; - -import org.apache.commons.lang.StringUtils; - -import java.net.URI; -import java.net.URISyntaxException; -import java.util.HashMap; -import java.util.Map; - -public class HgUrl { - - private static final Map SCHEMES = new HashMap(); - - private URI uri; - private HgUrlScheme scheme; - - private String username; - private String password; - - static { - SCHEMES.put("http", HgUrlScheme.HTTP); - SCHEMES.put("https", HgUrlScheme.HTTPS); - SCHEMES.put("ssh", HgUrlScheme.SSH); - SCHEMES.put("file", HgUrlScheme.FILE); - SCHEMES.put("", HgUrlScheme.FILE); - SCHEMES.put(null, HgUrlScheme.FILE); - } - - public HgUrl(String urlString) throws URISyntaxException { - uri = new URI(urlString).normalize(); - scheme = SCHEMES.get(uri.getScheme()); - } - - public boolean supportsAuthentication() { - return scheme.supportsAuthentication; - } - - public String getUsername() { - if (username == null) { - username = findUserInfoPart(0); - } - return username; - } - - public String getPassword() { - if (password == null) { - password = findUserInfoPart(1); - } - return password; - } - - public HgUrlScheme getScheme() { - return scheme; - } - - public void setUsername(String username) { - this.username = username; - } - - public void setPassword(String password) { - this.password = password; - } - - public String asString() throws URISyntaxException { - return asString(true); - } - - public String asString(boolean authData) throws URISyntaxException { - return new URI( - uri.getScheme(), - authData ? username + ":" + password : null, - uri.getHost(), - uri.getPort(), - uri.getPath(), - uri.getQuery(), - uri.getFragment() - ).toString(); - } - - private String findUserInfoPart(int index) { - String userInfo = uri.getUserInfo(); - if (StringUtils.isBlank(userInfo)) { - return null; - } - - String[] parts = StringUtils.splitPreserveAllTokens(userInfo, ':'); - if (parts.length > index && StringUtils.isNotBlank(parts[index])) { - return parts[index]; - } - return null; - } - - enum HgUrlScheme { - HTTP(true), - HTTPS(true), - SSH(true), - FILE(false); - - private final boolean supportsAuthentication; - - HgUrlScheme(boolean supportsAuthentication) { - this.supportsAuthentication = supportsAuthentication; - } - } -} - - diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgVersionCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgVersionCommand.java index 8f3e8346527f..60d07e69ea1f 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgVersionCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgVersionCommand.java @@ -13,6 +13,8 @@ package org.zmlx.hg4idea.command; import org.apache.commons.lang.StringUtils; +import org.zmlx.hg4idea.execution.ShellCommand; +import org.zmlx.hg4idea.execution.ShellCommandException; import java.nio.charset.Charset; import java.util.Arrays; diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgWorkingCopyRevisionsCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgWorkingCopyRevisionsCommand.java index ae762f2da1ae..db4e5de1ab98 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgWorkingCopyRevisionsCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgWorkingCopyRevisionsCommand.java @@ -23,6 +23,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.zmlx.hg4idea.HgRevisionNumber; import org.zmlx.hg4idea.HgUtil; +import org.zmlx.hg4idea.execution.HgCommandResult; +import org.zmlx.hg4idea.execution.HgCommandExecutor; import java.util.ArrayList; import java.util.Arrays; @@ -49,7 +51,7 @@ public class HgWorkingCopyRevisionsCommand { */ @NotNull public List parents(@NotNull VirtualFile repo) { - return getRevisions(repo, "parents", null, null); + return getRevisions(repo, "parents", null, null, true); } /** @@ -93,7 +95,7 @@ public class HgWorkingCopyRevisionsCommand { */ @NotNull public Pair parents(@NotNull VirtualFile repo, @Nullable FilePath file, @Nullable HgRevisionNumber revision) { - final List revisions = getRevisions(repo, "parents", file, revision); + final List revisions = getRevisions(repo, "parents", file, revision, true); switch (revisions.size()) { case 1: return Pair.create(revisions.get(0), null); case 2: return Pair.create(revisions.get(0), revisions.get(1)); @@ -115,7 +117,7 @@ public class HgWorkingCopyRevisionsCommand { @Nullable public HgRevisionNumber tip(@NotNull VirtualFile repo) { - List tips = getRevisions(repo, "tip", null, null); + List tips = getRevisions(repo, "tip", null, null, true); if (tips.size() > 1) { throw new IllegalStateException("There cannot be multiple tips"); } @@ -127,10 +129,9 @@ public class HgWorkingCopyRevisionsCommand { @Nullable public HgRevisionNumber identify(@NotNull VirtualFile repo) { - HgCommandService commandService = HgCommandService.getInstance(myProject); - HgCommandResult result = commandService.execute( - repo, "identify", Arrays.asList("--num", "--id") - ); + HgCommandExecutor commandExecutor = new HgCommandExecutor(myProject); + commandExecutor.setSilent(true); + HgCommandResult result = commandExecutor.executeInCurrentThread(repo, "identify", Arrays.asList("--num", "--id")); if (result == null) { return HgRevisionNumber.NULL_REVISION_NUMBER; } @@ -154,12 +155,14 @@ public class HgWorkingCopyRevisionsCommand { * @param command command to execute. * @param file file which revisions are wanted. If null then repository revisions are considered. * @param revision revision to execute on. If null then executed without the '-r' parameter, i.e. on the latest revision. + * @param silent pass true if this command shouldn't be mentioned in the VCS console. * @return List of revisions. */ private List getRevisions(@NotNull VirtualFile repo, @NotNull String command, @Nullable FilePath file, - @Nullable HgRevisionNumber revision) { + @Nullable HgRevisionNumber revision, + boolean silent) { final List args = new LinkedList(); args.add("--template"); args.add("{rev}|{node|short}\\n"); @@ -170,7 +173,9 @@ public class HgWorkingCopyRevisionsCommand { if (file != null) { // NB: this must be the last argument args.add(HgUtil.getOriginalFileName(file, ChangeListManager.getInstance(myProject)).getPath()); } - final HgCommandResult result = HgCommandService.getInstance(myProject).execute(repo, command, args); + final HgCommandExecutor executor = new HgCommandExecutor(myProject); + executor.setSilent(silent); + final HgCommandResult result = executor.executeInCurrentThread(repo, command, args); if (result == null) { return new ArrayList(0); diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/execution/HgCommandAuthenticator.java b/plugins/hg4idea/src/org/zmlx/hg4idea/execution/HgCommandAuthenticator.java new file mode 100644 index 000000000000..ad60053ae2c1 --- /dev/null +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/execution/HgCommandAuthenticator.java @@ -0,0 +1,186 @@ +// Copyright 2008-2010 Victor Iacoban +// +// 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.zmlx.hg4idea.execution; + +import com.intellij.ide.passwordSafe.PasswordSafe; +import com.intellij.ide.passwordSafe.PasswordSafeException; +import com.intellij.ide.passwordSafe.config.PasswordSafeSettings; +import com.intellij.ide.passwordSafe.impl.PasswordSafeImpl; +import com.intellij.ide.passwordSafe.impl.PasswordSafeProvider; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import org.apache.commons.lang.StringUtils; +import org.jetbrains.annotations.Nullable; +import org.zmlx.hg4idea.HgGlobalSettings; +import org.zmlx.hg4idea.HgVcs; +import org.zmlx.hg4idea.ui.HgUsernamePasswordDialog; + +import java.awt.*; +import java.lang.reflect.InvocationTargetException; + +/** + * Base class for any command interacting with a remote repository and which needs authentication. + */ +class HgCommandAuthenticator { + + private static final Logger LOG = Logger.getInstance(HgCommandAuthenticator.class.getName()); + + private GetPasswordRunnable myRunnable; + private final Project myProject; + + public HgCommandAuthenticator(Project project) { + myProject = project; + } + + public void saveCredentials() { + if (myRunnable == null) return; + + final PasswordSafeImpl passwordSafe = (PasswordSafeImpl)PasswordSafe.getInstance(); + if (passwordSafe.getSettings().getProviderType().equals(PasswordSafeSettings.ProviderType.DO_NOT_STORE)) { + return; + } + final String key = keyForUrlAndLogin(myRunnable.getURL(), myRunnable.getUserName()); + + final PasswordSafeProvider provider = + myRunnable.isRememberPassword() ? passwordSafe.getMasterKeyProvider() : passwordSafe.getMemoryProvider(); + try { + provider.storePassword(myProject, HgCommandAuthenticator.class, key, myRunnable.getPassword()); + final HgVcs vcs = HgVcs.getInstance(myProject); + if (vcs != null) { + vcs.getGlobalSettings().addRememberedUrl(myRunnable.getURL(), myRunnable.getUserName()); + } + } + catch (PasswordSafeException e) { + LOG.info("Couldn't store the password for key [" + key + "]", e); + } + } + + public boolean promptForAuthentication(Project project, String proposedLogin, String uri, String path) { + GetPasswordRunnable runnable = new GetPasswordRunnable(project, proposedLogin, uri, path); + // Don't use Application#invokeAndWait here, as IntelliJ + // may already be showing a dialog (such as the clone dialog) + try { + EventQueue.invokeAndWait(runnable); // TODO: use ApplicationManager.getApplication() with correct modality state. + myRunnable = runnable; + return runnable.isOk(); + } + catch (InterruptedException e) { + return false; + } + catch (InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + public String getUserName() { + return myRunnable.getUserName(); + } + + public String getPassword() { + return myRunnable.getPassword(); + } + + private static class GetPasswordRunnable implements Runnable { + private static final Logger LOG = Logger.getInstance(GetPasswordRunnable.class.getName()); + + private String myUserName; + private String myPassword; + private Project myProject; + private final String myProposedLogin; + private boolean ok = false; + @Nullable private String myURL; + private boolean myRememberPassword; + + public GetPasswordRunnable(Project project, String proposedLogin, String uri, String path) { + this.myProject = project; + this.myProposedLogin = proposedLogin; + this.myURL = uri + path; + } + + public void run() { + + // find if we've already been here + final HgVcs vcs = HgVcs.getInstance(myProject); + if (vcs == null) { return; } + + final HgGlobalSettings hgGlobalSettings = vcs.getGlobalSettings(); + @Nullable String rememberedLoginsForUrl = null; + if (!StringUtils.isBlank(myURL)) { + rememberedLoginsForUrl = hgGlobalSettings.getRememberedUserName(myURL); + } + + String login = myProposedLogin; + if (StringUtils.isBlank(login)) { + // find the last used login + login = rememberedLoginsForUrl; + } + + String password = null; + if (!StringUtils.isBlank(login) && myURL != null) { + // if we've logged in with this login, search for password + final String key = keyForUrlAndLogin(myURL, login); + try { + final PasswordSafeImpl passwordSafe = (PasswordSafeImpl)PasswordSafe.getInstance(); + password = passwordSafe.getMemoryProvider().getPassword(myProject, HgCommandAuthenticator.class, key); + if (password == null && passwordSafe.getSettings().getProviderType().equals(PasswordSafeSettings.ProviderType.MASTER_PASSWORD)) { + password = passwordSafe.getMasterKeyProvider().getPassword(myProject, HgCommandAuthenticator.class, key); + } + } catch (PasswordSafeException e) { + LOG.info("Couldn't get password for key [" + key + "]", e); + } + } + + // don't show dialog if we don't have to (both fields are known) + if (!StringUtils.isBlank(password) && !StringUtils.isBlank(login)) { + myUserName = login; + myPassword = password; + ok = true; + return; + } + + final HgUsernamePasswordDialog dialog = new HgUsernamePasswordDialog(myProject, myURL, login, password); + dialog.show(); + if (dialog.isOK()) { + myUserName = dialog.getUsername(); + myPassword = dialog.getPassword(); + myRememberPassword = dialog.isRememberPassword(); + ok = true; + } + } + + public String getUserName() { + return myUserName; + } + + public String getPassword() { + return myPassword; + } + + public boolean isOk() { + return ok; + } + + public String getURL() { + return myURL; + } + + public boolean isRememberPassword() { + return myRememberPassword; + } + } + + private static String keyForUrlAndLogin(String stringUrl, String login) { + return login + ":" + stringUrl; + } + +} diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCommandException.java b/plugins/hg4idea/src/org/zmlx/hg4idea/execution/HgCommandException.java similarity index 96% rename from plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCommandException.java rename to plugins/hg4idea/src/org/zmlx/hg4idea/execution/HgCommandException.java index 8b7b5507e662..1d20a7ee7260 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCommandException.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/execution/HgCommandException.java @@ -10,7 +10,7 @@ // 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.zmlx.hg4idea.command; +package org.zmlx.hg4idea.execution; public class HgCommandException extends Exception { diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCommandService.java b/plugins/hg4idea/src/org/zmlx/hg4idea/execution/HgCommandExecutor.java similarity index 52% rename from plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCommandService.java rename to plugins/hg4idea/src/org/zmlx/hg4idea/execution/HgCommandExecutor.java index e5ad4ec72fa2..385e390e8b77 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCommandService.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/execution/HgCommandExecutor.java @@ -10,89 +10,93 @@ // 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.zmlx.hg4idea.command; +package org.zmlx.hg4idea.execution; import com.intellij.execution.ui.ConsoleViewContentType; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.vcsUtil.VcsUtil; import org.apache.commons.lang.StringUtils; import org.jetbrains.annotations.Nullable; -import org.zmlx.hg4idea.HgExecutableValidator; -import org.zmlx.hg4idea.HgGlobalSettings; -import org.zmlx.hg4idea.HgUtil; -import org.zmlx.hg4idea.HgVcs; -import org.zmlx.hg4idea.HgVcsMessages; +import org.zmlx.hg4idea.*; import javax.swing.*; import java.awt.*; import java.io.DataInputStream; import java.io.DataOutputStream; -import java.io.File; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.net.Socket; -import java.net.URI; import java.nio.charset.Charset; -import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; import java.util.List; -public final class HgCommandService { +/** + *

Executes an hg external command synchronously or asynchronously with the consequent call of {@link HgCommandResultHandler}

+ * + *

Silence policy: + *

  • if the command is silent, the fact of its execution will be recorded in the log, but not in the VCS console. + *
  • if the command is not silent, which is default, it is written in the log and console. + *
  • the command output is not written to the log or shown to console by default, but it can be changed via {@link #myShowOutput} + *
  • error output is logged to the console and log, if the command is not silent. + *

    + */ +public final class HgCommandExecutor { - private static File PROMPT_HOOKS_PLUGIN; - - static final Logger LOG = Logger.getInstance(HgCommandService.class.getName()); - - static final List DEFAULT_OPTIONS = Arrays.asList( - "--config", "ui.merge=internal:merge" - ); + private static final Logger LOG = Logger.getInstance(HgCommandExecutor.class.getName()); + private static final List DEFAULT_OPTIONS = Arrays.asList("--config", "ui.merge=internal:merge"); private final Project myProject; private final HgGlobalSettings mySettings; - private HgExecutableValidator myValidator; - private HgVcs myVcs; + private final HgExecutableValidator myValidator; + private final HgVcs myVcs; - public HgCommandService(Project project, HgGlobalSettings settings) { + private Charset myCharset = Charset.defaultCharset(); + private boolean myIsSilent = false; + private boolean myShowOutput = false; + private List myOptions = DEFAULT_OPTIONS; + + public HgCommandExecutor(Project project) { myProject = project; - mySettings = settings; - if (PROMPT_HOOKS_PLUGIN == null) { - PROMPT_HOOKS_PLUGIN = HgUtil.getTemporaryPythonFile("prompthooks"); - } myVcs = HgVcs.getInstance(myProject); - LOG.assertTrue(myVcs != null); + mySettings = myVcs.getGlobalSettings(); myValidator = myVcs.getExecutableValidator(); } - public static HgCommandService getInstance(Project project) { - return ServiceManager.getService(project, HgCommandService.class); + public void setCharset(Charset charset) { + myCharset = charset; + } + + public void setSilent(boolean isSilent) { + myIsSilent = isSilent; + } + + public void setOptions(List options) { + myOptions = options; + } + + public void setShowOutput(boolean showOutput) { + myShowOutput = showOutput; + } + + public void execute(@Nullable final VirtualFile repo, final String operation, final List arguments, @Nullable final HgCommandResultHandler handler) { + ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { + @Override + public void run() { + final HgCommandResult result = executeInCurrentThread(repo, operation, arguments); + if (handler != null) { + handler.process(result); + } + } + }); } @Nullable - HgCommandResult execute(VirtualFile repo, String operation, List arguments) { - return execute( - repo, DEFAULT_OPTIONS, operation, arguments, Charset.defaultCharset() - ); - } - - @Nullable - HgCommandResult execute(VirtualFile repo, List hgOptions, - String operation, List arguments) { - return execute(repo, hgOptions, operation, arguments, Charset.defaultCharset()); - } - - @Nullable - HgCommandResult execute(VirtualFile repo, List hgOptions, - String operation, List arguments, Charset charset) { - return execute(repo, hgOptions, operation, arguments, charset, false); - } - - @Nullable - HgCommandResult execute(VirtualFile repo, List hgOptions, String operation, List arguments, Charset charset, boolean silent) { + public HgCommandResult executeInCurrentThread(@Nullable final VirtualFile repo, final String operation, final List arguments) { + LOG.assertTrue(!ApplicationManager.getApplication().isDispatchThread()); if (myProject.isDisposed()) { return null; } @@ -104,21 +108,25 @@ public final class HgCommandService { cmdLine.add(repo.getPath()); } - SocketServer promptServer = new SocketServer(new PromptReceiver()); WarningReceiver warningReceiver = new WarningReceiver(); + PassReceiver passReceiver = new PassReceiver(myProject); + + SocketServer promptServer = new SocketServer(new PromptReceiver()); SocketServer warningServer = new SocketServer(warningReceiver); - if (PROMPT_HOOKS_PLUGIN == null) { - throw new RuntimeException("Could not hook into the prompt mechanism of Mercurial"); - } + SocketServer passServer = new SocketServer(passReceiver); + try { int promptPort = promptServer.start(); int warningPort = warningServer.start(); + int passPort = passServer.start(); cmdLine.add("--config"); - cmdLine.add("extensions.hg4ideapromptextension=" + PROMPT_HOOKS_PLUGIN.getAbsolutePath()); + cmdLine.add("extensions.hg4ideapromptextension=" + myVcs.getPromptHooksExtensionFile().getAbsolutePath()); cmdLine.add("--config"); cmdLine.add("hg4ideaprompt.port=" + promptPort); cmdLine.add("--config"); cmdLine.add("hg4ideawarn.port=" + warningPort); + cmdLine.add("--config"); + cmdLine.add("hg4ideapass.port=" + passPort); // Other parts of the plugin count on the availability of the MQ extension, so make sure it is enabled cmdLine.add("--config"); @@ -126,9 +134,12 @@ public final class HgCommandService { } catch (IOException e) { showError(e); LOG.info("IOException during preparing command", e); + promptServer.stop(); + warningServer.stop(); + passServer.stop(); return null; } - cmdLine.addAll(hgOptions); + cmdLine.addAll(myOptions); cmdLine.add(operation); if (arguments != null && arguments.size() != 0) { cmdLine.addAll(arguments); @@ -137,9 +148,12 @@ public final class HgCommandService { HgCommandResult result; try { String workingDir = repo != null ? repo.getPath() : null; - result = shellCommand.execute(cmdLine, workingDir, charset); + result = shellCommand.execute(cmdLine, workingDir, myCharset); + if (!HgErrorUtil.isAuthorizationError(result)) { + passReceiver.saveCredentials(); + } } catch (ShellCommandException e) { - if (!silent) { + if (!myIsSilent) { if (myValidator.checkExecutableAndNotifyIfNeeded()) { // if the problem was not with invalid executable - show error. showError(e); @@ -155,49 +169,37 @@ public final class HgCommandService { } finally { promptServer.stop(); warningServer.stop(); + passServer.stop(); } String warnings = warningReceiver.getWarnings(); result.setWarnings(warnings); - // logging to the Version Control console (without extensions and configs) - final String cmdString = String.format("%s %s %s", mySettings.isRunViaBash() ? "bash -c " + HgVcs.HG_EXECUTABLE_FILE_NAME : HgVcs.HG_EXECUTABLE_FILE_NAME, operation, - StringUtils.join(maskAuthInfoFromUrl(arguments), " ")); - myVcs.showMessageInConsole(cmdString, ConsoleViewContentType.NORMAL_OUTPUT.getAttributes()); - LOG.info(cmdString); - if (!silent) { - myVcs.showMessageInConsole(result.getRawOutput(), ConsoleViewContentType.SYSTEM_OUTPUT.getAttributes()); - LOG.info(result.getRawOutput()); - } - myVcs.showMessageInConsole(result.getRawError(), ConsoleViewContentType.ERROR_OUTPUT.getAttributes()); - LOG.info(result.getRawError()); - + log(operation, arguments, result); return result; } - /** - * Strips possible authentication information from arguments passed to the command line - * to prevent private information appear in the VCS console or logs. - * @param arguments command line arguments. - * @return command line arguments which don't contain authentication information. - */ - private static List maskAuthInfoFromUrl(List arguments) { - if (arguments == null || arguments.isEmpty()) { - return arguments; + // logging to the Version Control console (without extensions and configs) + private void log(String operation, List arguments, HgCommandResult result) { + final String executable = mySettings.isRunViaBash() ? "bash -c " + HgVcs.HG_EXECUTABLE_FILE_NAME : HgVcs.HG_EXECUTABLE_FILE_NAME; + final String cmdString = String.format("%s %s %s", executable, operation, StringUtils.join(arguments, " ")); + + // log command + LOG.info(cmdString); + if (!myIsSilent) { + myVcs.showMessageInConsole(cmdString, ConsoleViewContentType.NORMAL_OUTPUT.getAttributes()); } - final List newArgs = new ArrayList(arguments.size()); - for (String arg : arguments) { - if (!arg.contains("@")) { // simple filter - newArgs.add(arg); - } else { - try { - final URI uri = new URI(arg); // parsing via URI methods, exception means it's not an URI - newArgs.add(uri.toString().replace(uri.getUserInfo(), ":")); - } catch (Throwable e) { - newArgs.add(arg); - } - } + + // log output if needed + if (!myIsSilent && myShowOutput) { + LOG.info(result.getRawOutput()); + myVcs.showMessageInConsole(result.getRawOutput(), ConsoleViewContentType.SYSTEM_OUTPUT.getAttributes()); + } + + // log error + if (!myIsSilent) { + LOG.info(result.getRawError()); + myVcs.showMessageInConsole(result.getRawError(), ConsoleViewContentType.ERROR_OUTPUT.getAttributes()); } - return newArgs; } private void showError(Exception e) { @@ -241,6 +243,7 @@ public final class HgCommandService { public boolean handleConnection(Socket socket) throws IOException { DataInputStream dataInput = new DataInputStream(socket.getInputStream()); + DataOutputStream out = new DataOutputStream(socket.getOutputStream()); final String message = new String(readDataBlock(dataInput)); int numOfChoices = dataInput.readInt(); final Choice[] choices = new Choice[numOfChoices]; @@ -252,29 +255,32 @@ public final class HgCommandService { final Choice defaultChoice = choices[defaultChoiceInt]; final int[] index = new int[]{-1}; - ApplicationManager.getApplication().invokeAndWait(new Runnable() { - public void run() { - Window parent = ApplicationManager.getApplication().getComponent(Window.class); - index[0] = JOptionPane.showOptionDialog( - parent, - message, - "hg4idea", - JOptionPane.OK_CANCEL_OPTION, - JOptionPane.QUESTION_MESSAGE, - null, - choices, - defaultChoice); + try { + EventQueue.invokeAndWait(new Runnable() { + public void run() { + Window parent = ApplicationManager.getApplication().getComponent(Window.class); + index[0] = JOptionPane + .showOptionDialog(parent, message, "hg4idea", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, choices, + defaultChoice); + } + }); + + int chosen = index[0]; + if (chosen == JOptionPane.CLOSED_OPTION) { + out.writeInt(-1); + } else { + out.writeInt(chosen); } - }, ModalityState.defaultModalityState()); - - int chosen = index[0]; - DataOutputStream out = new DataOutputStream(socket.getOutputStream()); - if (chosen == JOptionPane.CLOSED_OPTION) { - out.writeInt(-1); - } else { - out.writeInt(chosen); + return true; + } + catch (InterruptedException e) { + //do nothing + return true; + } + catch (InvocationTargetException e) { + //shouldn't happen + throw new RuntimeException(e); } - return true; } private static class Choice{ @@ -313,6 +319,39 @@ public final class HgCommandService { } } } + + private static class PassReceiver extends SocketServer.Protocol{ + private final Project myProject; + private HgCommandAuthenticator myAuthenticator; + private PassReceiver(Project project) { + myProject = project; + } + @Override + public boolean handleConnection(Socket socket) throws IOException { + DataInputStream dataInputStream = new DataInputStream(socket.getInputStream()); + DataOutputStream out = new DataOutputStream(socket.getOutputStream()); + + String command = new String(readDataBlock(dataInputStream)); + assert "getpass".equals(command); + String uri = new String(readDataBlock(dataInputStream)); + String path = new String(readDataBlock(dataInputStream)); + String proposedLogin = new String(readDataBlock(dataInputStream)); + + HgCommandAuthenticator authenticator = new HgCommandAuthenticator(myProject); + boolean ok = authenticator.promptForAuthentication(myProject, proposedLogin, uri, path); + if (ok) { + myAuthenticator = authenticator; + sendDataBlock(out, authenticator.getUserName().getBytes()); + sendDataBlock(out, authenticator.getPassword().getBytes()); + } + return true; + } + + public void saveCredentials() { + if (myAuthenticator == null) return; + myAuthenticator.saveCredentials(); + } + } } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCommandResult.java b/plugins/hg4idea/src/org/zmlx/hg4idea/execution/HgCommandResult.java similarity index 98% rename from plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCommandResult.java rename to plugins/hg4idea/src/org/zmlx/hg4idea/execution/HgCommandResult.java index 8057b46f08f1..8ef1a7001540 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCommandResult.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/execution/HgCommandResult.java @@ -10,7 +10,7 @@ // 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.zmlx.hg4idea.command; +package org.zmlx.hg4idea.execution; import com.intellij.openapi.util.text.LineTokenizer; diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCommandResultListener.java b/plugins/hg4idea/src/org/zmlx/hg4idea/execution/HgCommandResultHandler.java similarity index 64% rename from plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCommandResultListener.java rename to plugins/hg4idea/src/org/zmlx/hg4idea/execution/HgCommandResultHandler.java index 4f86db5328ac..d1dee96539de 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgCommandResultListener.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/execution/HgCommandResultHandler.java @@ -10,10 +10,18 @@ // 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.zmlx.hg4idea.command; +package org.zmlx.hg4idea.execution; -public interface HgCommandResultListener { +import org.jetbrains.annotations.Nullable; - void process(HgCommandResult result); +public interface HgCommandResultHandler { + + HgCommandResultHandler DUMB = new HgCommandResultHandler() { + @Override + public void process(@Nullable HgCommandResult result) { + } + }; + + void process(@Nullable HgCommandResult result); } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/ShellCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/execution/ShellCommand.java similarity index 74% rename from plugins/hg4idea/src/org/zmlx/hg4idea/command/ShellCommand.java rename to plugins/hg4idea/src/org/zmlx/hg4idea/execution/ShellCommand.java index e87389fcbda3..f89df8c18043 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/ShellCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/execution/ShellCommand.java @@ -10,8 +10,10 @@ // 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.zmlx.hg4idea.command; +package org.zmlx.hg4idea.execution; +import com.intellij.execution.process.CapturingProcessHandler; +import com.intellij.execution.process.ProcessOutput; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.text.StringUtil; @@ -21,7 +23,7 @@ import java.util.ArrayList; import java.util.List; -final class ShellCommand { +public final class ShellCommand { private static final Logger LOG = Logger.getInstance(ShellCommand.class.getName()); @@ -56,48 +58,19 @@ final class ShellCommand { processBuilder = processBuilder.directory(new File(dir)); } Process process = processBuilder.start(); - Thread outReaderThread = startReader( - new InputStreamReader(process.getInputStream(), charset), out - ); - Thread errReaderThread = startReader( - new InputStreamReader(process.getErrorStream()), err - ); - process.waitFor(); - int exitValue = process.exitValue(); - outReaderThread.join(); - errReaderThread.join(); + + CapturingProcessHandler processHandler = new CapturingProcessHandler(process); + final ProcessOutput processOutput = processHandler.runProcess(); + + int exitValue = processOutput.getExitCode(); + out.write(processOutput.getStdout()); + err.write(processOutput.getStderr()); return new HgCommandResult(out, err, exitValue ); } catch (IOException e) { throw new ShellCommandException(e); } } - private Thread startReader(final InputStreamReader in, final Writer writer) { - Thread readingThread = new Thread(new Runnable() { - public void run() { - char[] buffer = new char[BUFFER_SIZE]; - int count; - try { - while ((count = in.read(buffer)) > 0) { - writer.write(buffer, 0, count); - } - writer.flush(); - } catch (IOException e) { - LOG.info(e.getMessage()); - } finally { - try { - in.close(); - } - catch (IOException e) { - // ignore - } - } - } - }); - readingThread.start(); - return readingThread; - } - /** * Escapes charactes in the command which will be executed via 'bash -c' - these are standard chars like \n, and some bash specials. * @param source Original string. diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/ShellCommandException.java b/plugins/hg4idea/src/org/zmlx/hg4idea/execution/ShellCommandException.java similarity index 96% rename from plugins/hg4idea/src/org/zmlx/hg4idea/command/ShellCommandException.java rename to plugins/hg4idea/src/org/zmlx/hg4idea/execution/ShellCommandException.java index 01c4dd7b282c..cf362677e5e6 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/ShellCommandException.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/execution/ShellCommandException.java @@ -10,7 +10,7 @@ // 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.zmlx.hg4idea.command; +package org.zmlx.hg4idea.execution; public class ShellCommandException extends Exception { ShellCommandException() { diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/SocketServer.java b/plugins/hg4idea/src/org/zmlx/hg4idea/execution/SocketServer.java similarity index 74% rename from plugins/hg4idea/src/org/zmlx/hg4idea/command/SocketServer.java rename to plugins/hg4idea/src/org/zmlx/hg4idea/execution/SocketServer.java index 51bcf40eda9b..2e2df465ec7c 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/SocketServer.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/execution/SocketServer.java @@ -10,7 +10,9 @@ // 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.zmlx.hg4idea.command; +package org.zmlx.hg4idea.execution; + +import com.intellij.openapi.application.ApplicationManager; import java.io.DataInputStream; import java.io.DataOutputStream; @@ -18,54 +20,59 @@ import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; +import java.util.concurrent.Future; /** * Common server class that contains the boiler-plate code to set up a server socket. * The actual logic is delegated to the Protocol instance. */ public class SocketServer { - protected Thread serverThread; - protected ServerSocket serverSocket; - private final Protocol protocol; + protected ServerSocket myServerSocket; + private final Protocol myProtocol; + private Future myExecutingFuture; public SocketServer(Protocol protocol) { - this.protocol = protocol; + myProtocol = protocol; } public int start() throws IOException { - serverSocket = new ServerSocket(0); - int port = serverSocket.getLocalPort(); + myServerSocket = new ServerSocket(0); + int port = myServerSocket.getLocalPort(); - String name = "hg4idea prompt server"; - serverThread = new Thread(name) { + myExecutingFuture = ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { @Override public void run() { try { boolean _continue = true; while (_continue) { - Socket socket = serverSocket.accept(); - _continue = protocol.handleConnection(socket); + Socket socket = myServerSocket.accept(); + try { + _continue = myProtocol.handleConnection(socket); + } + finally { + socket.close(); + } } - } catch (SocketException e) { + } + catch (SocketException e) { //socket was closed, that's OK - } catch (IOException e) { + } + catch (IOException e) { throw new RuntimeException(e); //TODO implement catch clause } } - }; - serverThread.start(); + }); return port; } public void stop() { - serverThread.interrupt(); + myExecutingFuture.cancel(true); try { - serverSocket.close(); + myServerSocket.close(); } catch (IOException e) { throw new RuntimeException(e); //TODO implement catch clause } - } public static abstract class Protocol { diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/provider/HgChangeProvider.java b/plugins/hg4idea/src/org/zmlx/hg4idea/provider/HgChangeProvider.java index 8f73dc1a078a..335eea912b0b 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/provider/HgChangeProvider.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/provider/HgChangeProvider.java @@ -20,9 +20,7 @@ import com.intellij.openapi.vcs.*; import com.intellij.openapi.vcs.changes.*; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.vcsUtil.VcsUtil; -import org.zmlx.hg4idea.HgContentRevision; -import org.zmlx.hg4idea.HgFile; -import org.zmlx.hg4idea.HgRevisionNumber; +import org.zmlx.hg4idea.*; import org.zmlx.hg4idea.command.*; import java.awt.*; @@ -90,7 +88,7 @@ public class HgChangeProvider implements ChangeProvider { return new HashSet(); } sendChanges(builder, hgChanges, - new HgResolveCommand(myProject).list(repo), + new HgResolveCommand(myProject).getListSynchronously(repo), new HgWorkingCopyRevisionsCommand(myProject).identify(repo), new HgWorkingCopyRevisionsCommand(myProject).firstParent(repo) ); diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/provider/HgCheckoutProvider.java b/plugins/hg4idea/src/org/zmlx/hg4idea/provider/HgCheckoutProvider.java index 2b3207948431..d368236b3872 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/provider/HgCheckoutProvider.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/provider/HgCheckoutProvider.java @@ -24,7 +24,6 @@ import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vcs.CheckoutProvider; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; @@ -33,16 +32,10 @@ import org.jetbrains.annotations.Nullable; import org.zmlx.hg4idea.HgVcs; import org.zmlx.hg4idea.HgVcsMessages; import org.zmlx.hg4idea.command.HgCloneCommand; -import org.zmlx.hg4idea.command.HgCommandResult; +import org.zmlx.hg4idea.execution.HgCommandResult; import org.zmlx.hg4idea.ui.HgCloneDialog; -import java.io.BufferedReader; import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.io.PrintWriter; -import java.net.URI; /** * Checkout provider for Mercurial @@ -78,107 +71,27 @@ public class HgCheckoutProvider implements CheckoutProvider { clone.setDirectory(targetDir); // handle result - try { - final HgCommandResult myCloneResult = clone.execute(); - if (myCloneResult == null) { - notifyError("Clone failed", "Clone failed due to unknown error", project); - } else if (myCloneResult.getExitValue() != 0) { - notifyError("Clone failed", "Clone from " + sourceRepositoryURL + " failed.

    " + myCloneResult.getRawError(), project); - } else { - ApplicationManager.getApplication().invokeLater(new Runnable() { - @Override - public void run() { - if (listener != null) { - listener.directoryCheckedOut(new File(dialog.getParentDirectory(), dialog.getDirectoryName())); - listener.checkoutCompleted(); - } + final HgCommandResult myCloneResult = clone.execute(); + if (myCloneResult == null) { + notifyError("Clone failed", "Clone failed due to unknown error", project); + } else if (myCloneResult.getExitValue() != 0) { + notifyError("Clone failed", "Clone from " + sourceRepositoryURL + " failed.

    " + myCloneResult.getRawError(), project); + } else { + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + if (listener != null) { + listener.directoryCheckedOut(new File(dialog.getParentDirectory(), dialog.getDirectoryName())); + listener.checkoutCompleted(); } - }); - } - } finally { - cleanupAuthDataFromHgrc(targetDir); + } + }); } } }.queue(); } - /** - * Removes authentication data from the parent URL of a just cloned repository. - * @param targetDir directory where the hg project was cloned into. - */ - private static void cleanupAuthDataFromHgrc(String targetDir) { - File hgrc = new File(new File(targetDir, ".hg"), "hgrc"); - if (!hgrc.exists()) { - return; - } - - for (int i = 0; i < 3; i++) { // 3 attempts in case of an IOException - BufferedReader reader = null; - PrintWriter writer = null; - try { - // writing correct info into a temporary file - final File tempFile = FileUtil.createTempFile("hgrc", "temp"); - tempFile.deleteOnExit(); - reader = new BufferedReader(new FileReader(hgrc)); - writer = new PrintWriter(new FileWriter(tempFile)); - String line; - while ((line = reader.readLine()) != null) { - String parseLine = line.trim(); - if (parseLine.startsWith("default") && parseLine.contains("@")) { // looking for paths.default - int eqIdx = parseLine.indexOf('='); - parseLine = parseLine.substring(eqIdx+1).trim(); // getting value of paths.default - try { - final URI uri = new URI(parseLine); - final String userInfo = uri.getUserInfo(); - if (userInfo == null) { // no user info => OK - writer.println(line); - continue; - } - int colonIdx = userInfo.indexOf(':'); - if (colonIdx == -1) { // no password given => OK - writer.println(line); - continue; - } - final String urlWithoutAuthData = uri.toString().replace(userInfo, userInfo.substring(0, colonIdx)); // remove password, leave username - writer.println("default = " + urlWithoutAuthData); - } catch (Throwable t) { // not URI => no sensitive data - writer.println(line); - } - } else { - writer.println(line); - } - } - reader.close(); - writer.close(); - - // substituting files - try { - if (!tempFile.renameTo(hgrc)) { // this may fail in case of different FSs - FileUtil.copy(tempFile, hgrc); - FileUtil.delete(tempFile); - } - } catch (Throwable e) { - FileUtil.copy(tempFile, hgrc); - FileUtil.delete(tempFile); - } - return; - } catch (IOException e) { - LOG.info(e); - } finally { - if (reader != null) { - try { - reader.close(); - } catch (IOException e) { - } - } - if (writer != null) { - writer.close(); - } - } - } - } - private static void notifyError(String title, String description, Project project) { Notifications.Bus.notify(new Notification(HgVcs.NOTIFICATION_GROUP_ID, title, description, NotificationType.ERROR), project); } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/provider/HgHistoryProvider.java b/plugins/hg4idea/src/org/zmlx/hg4idea/provider/HgHistoryProvider.java index 7a706efad127..a6690f64f6de 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/provider/HgHistoryProvider.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/provider/HgHistoryProvider.java @@ -66,7 +66,7 @@ public class HgHistoryProvider implements VcsHistoryProvider { } final List revisions = new ArrayList(); revisions.addAll(getHistory(filePath, vcsRoot, myProject, DEFAULT_LIMIT)); - return createAppendableSession(vcsRoot, revisions); + return createAppendableSession(vcsRoot, revisions, null); } public void reportAppendableHistory(FilePath filePath, final VcsAppendableHistorySessionPartner partner) throws VcsException { @@ -75,7 +75,7 @@ public class HgHistoryProvider implements VcsHistoryProvider { final List history = getHistory(filePath, vcsRoot, myProject, DEFAULT_LIMIT); if (history.size() == 0) return; - final VcsAbstractHistorySession emptySession = createAppendableSession(vcsRoot, Collections.emptyList()); + final VcsAbstractHistorySession emptySession = createAppendableSession(vcsRoot, Collections.emptyList(), null); partner.reportCreatedEmptySession(emptySession); for (HgFileRevision hgFileRevision : history) { @@ -84,8 +84,8 @@ public class HgHistoryProvider implements VcsHistoryProvider { partner.finished(); } - private VcsAbstractHistorySession createAppendableSession(final VirtualFile vcsRoot, List revisions) { - return new VcsAbstractHistorySession(revisions) { + private VcsAbstractHistorySession createAppendableSession(final VirtualFile vcsRoot, List revisions, @Nullable VcsRevisionNumber number) { + return new VcsAbstractHistorySession(revisions, number) { @Nullable protected VcsRevisionNumber calcCurrentRevisionNumber() { return new HgWorkingCopyRevisionsCommand(myProject).firstParent(vcsRoot); @@ -97,7 +97,7 @@ public class HgHistoryProvider implements VcsHistoryProvider { @Override public VcsHistorySession copy() { - return createAppendableSession(vcsRoot, getRevisionList()); + return createAppendableSession(vcsRoot, getRevisionList(), getCurrentRevisionNumber()); } }; } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/provider/commit/HgCheckinEnvironment.java b/plugins/hg4idea/src/org/zmlx/hg4idea/provider/commit/HgCheckinEnvironment.java index f1e0c624394d..0a85a46b1ea5 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/provider/commit/HgCheckinEnvironment.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/provider/commit/HgCheckinEnvironment.java @@ -30,10 +30,12 @@ import com.intellij.util.NullableFunction; import com.intellij.util.PairConsumer; import com.intellij.vcsUtil.VcsUtil; import org.jetbrains.annotations.NotNull; +import org.zmlx.hg4idea.HgChange; import org.zmlx.hg4idea.HgFile; import org.zmlx.hg4idea.HgRevisionNumber; import org.zmlx.hg4idea.HgVcsMessages; import org.zmlx.hg4idea.command.*; +import org.zmlx.hg4idea.execution.HgCommandException; import java.util.*; @@ -175,13 +177,18 @@ public class HgCheckinEnvironment implements CheckinEnvironment { } public List scheduleUnversionedFilesForAddition(List files) { - HgAddCommand command = new HgAddCommand(project); - for (VirtualFile file : files) { - VirtualFile vcsRoot = VcsUtil.getVcsRootFor(project, file); + final HgAddCommand command = new HgAddCommand(project); + for (final VirtualFile file : files) { + final VirtualFile vcsRoot = VcsUtil.getVcsRootFor(project, file); if (vcsRoot == null) { continue; } - command.execute(new HgFile(vcsRoot, VfsUtil.virtualToIoFile(file))); + ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { + @Override + public void run() { + command.execute(new HgFile(vcsRoot, VfsUtil.virtualToIoFile(file))); + } + }); } return null; } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/provider/update/HgConflictResolver.java b/plugins/hg4idea/src/org/zmlx/hg4idea/provider/update/HgConflictResolver.java index d23d4ab9771c..87e346646c3e 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/provider/update/HgConflictResolver.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/provider/update/HgConflictResolver.java @@ -13,6 +13,7 @@ package org.zmlx.hg4idea.provider.update; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.project.Project; import com.intellij.openapi.vcs.AbstractVcsHelper; import com.intellij.openapi.vcs.update.FileGroup; @@ -45,7 +46,7 @@ public final class HgConflictResolver { } public void resolve(final VirtualFile repo) { - Map resolves = new HgResolveCommand(myProject).list(repo); + Map resolves = new HgResolveCommand(myProject).getListSynchronously(repo); final List conflicts = new ArrayList(); for (Map.Entry entry : resolves.entrySet()) { File file = entry.getKey().getFile(); @@ -72,11 +73,13 @@ public final class HgConflictResolver { return; } - if (!ApplicationManager.getApplication().isUnitTestMode()) { - final HgVcs vcs = HgVcs.getInstance(myProject); - if (vcs == null) { return; } - AbstractVcsHelper.getInstance(myProject).showMergeDialog(conflicts, vcs.getMergeProvider()); - } + final HgVcs vcs = HgVcs.getInstance(myProject); + if (vcs == null) { return; } + ApplicationManager.getApplication().invokeAndWait(new Runnable() { + public void run() { + AbstractVcsHelper.getInstance(myProject).showMergeDialog(conflicts, vcs.getMergeProvider()); + } + }, ModalityState.defaultModalityState()); } } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/provider/update/HgHeadMerger.java b/plugins/hg4idea/src/org/zmlx/hg4idea/provider/update/HgHeadMerger.java index 039fce499955..7120af385c8d 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/provider/update/HgHeadMerger.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/provider/update/HgHeadMerger.java @@ -20,11 +20,11 @@ import com.intellij.openapi.vcs.update.FileGroup; import com.intellij.openapi.vcs.update.UpdatedFiles; import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NotNull; +import org.zmlx.hg4idea.HgChange; import org.zmlx.hg4idea.HgFile; import org.zmlx.hg4idea.HgUtil; import org.zmlx.hg4idea.HgVcs; -import org.zmlx.hg4idea.command.HgChange; -import org.zmlx.hg4idea.command.HgCommandResult; +import org.zmlx.hg4idea.execution.HgCommandResult; import org.zmlx.hg4idea.command.HgMergeCommand; import org.zmlx.hg4idea.command.HgStatusCommand; diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/provider/update/HgIntegrateEnvironment.java b/plugins/hg4idea/src/org/zmlx/hg4idea/provider/update/HgIntegrateEnvironment.java index 2be3934e75c1..1a9b30e9aae2 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/provider/update/HgIntegrateEnvironment.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/provider/update/HgIntegrateEnvironment.java @@ -12,8 +12,6 @@ // limitations under the License. package org.zmlx.hg4idea.provider.update; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.options.Configurable; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; @@ -97,11 +95,7 @@ public class HgIntegrateEnvironment implements UpdateEnvironment { exceptions.add(warning); } - ApplicationManager.getApplication().invokeAndWait(new Runnable() { - public void run() { - new HgConflictResolver(project, updatedFiles).resolve(repo); - } - }, ModalityState.defaultModalityState()); + new HgConflictResolver(project, updatedFiles).resolve(repo); } catch (VcsException e) { exceptions.add(e); diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/provider/update/HgRegularUpdater.java b/plugins/hg4idea/src/org/zmlx/hg4idea/provider/update/HgRegularUpdater.java index b192a8d98e2d..bd25ba3db6a8 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/provider/update/HgRegularUpdater.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/provider/update/HgRegularUpdater.java @@ -12,8 +12,7 @@ // limitations under the License. package org.zmlx.hg4idea.provider.update; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; import com.intellij.openapi.vcs.VcsException; @@ -22,10 +21,12 @@ import com.intellij.openapi.vcs.update.UpdatedFiles; import com.intellij.openapi.vfs.VirtualFile; import org.apache.commons.lang.StringUtils; import org.jetbrains.annotations.NotNull; -import org.zmlx.hg4idea.HgRevisionNumber; -import org.zmlx.hg4idea.HgVcs; -import org.zmlx.hg4idea.HgVcsMessages; +import org.jetbrains.annotations.Nullable; +import org.zmlx.hg4idea.*; import org.zmlx.hg4idea.command.*; +import org.zmlx.hg4idea.execution.HgCommandException; +import org.zmlx.hg4idea.execution.HgCommandResult; +import org.zmlx.hg4idea.execution.HgCommandResultHandler; import java.util.ArrayList; import java.util.List; @@ -38,6 +39,7 @@ public class HgRegularUpdater implements HgUpdater { private final Project project; @NotNull private final VirtualFile repository; @NotNull private final UpdateConfiguration updateConfiguration; + private static final Logger LOG = Logger.getInstance(HgRegularUpdater.class); public HgRegularUpdater(Project project, @NotNull VirtualFile repository, @NotNull UpdateConfiguration configuration) { this.project = project; @@ -197,11 +199,7 @@ public class HgRegularUpdater implements HgUpdater { } private void resolvePossibleConflicts(final UpdatedFiles updatedFiles) { - ApplicationManager.getApplication().invokeAndWait(new Runnable() { - public void run() { - new HgConflictResolver(project, updatedFiles).resolve(repository); - } - }, ModalityState.defaultModalityState()); + new HgConflictResolver(project, updatedFiles).resolve(repository); } private Set getLocalChanges() { @@ -218,7 +216,17 @@ public class HgRegularUpdater implements HgUpdater { hgPullCommand.setSource(new HgShowConfigCommand(project).getDefaultPath(repo)); hgPullCommand.setUpdate(false); hgPullCommand.setRebase(false); - ensureSuccess(hgPullCommand.execute()); + hgPullCommand.execute(new HgCommandResultHandler() { + @Override + public void process(@Nullable HgCommandResult result) { + try { + ensureSuccess(result); + } + catch (VcsException e) { + LOG.error(e); + } + } + }); } private void update(@NotNull VirtualFile repo, ProgressIndicator indicator, UpdatedFiles updatedFiles, List warnings) throws VcsException { diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/ui/HgCloneDialog.java b/plugins/hg4idea/src/org/zmlx/hg4idea/ui/HgCloneDialog.java index 2986e714f03a..1e562c1aa8e4 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/ui/HgCloneDialog.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/ui/HgCloneDialog.java @@ -15,13 +15,17 @@ */ package org.zmlx.hg4idea.ui; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.fileChooser.FileChooserDescriptor; +import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.*; import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; import org.zmlx.hg4idea.HgVcsMessages; -import org.zmlx.hg4idea.command.HgCommandResult; +import org.zmlx.hg4idea.execution.HgCommandResult; import org.zmlx.hg4idea.command.HgIdentifyCommand; import javax.swing.*; @@ -180,21 +184,22 @@ public class HgCloneDialog extends DialogWrapper { testButton.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { testURL = repositoryURL.getText(); - final boolean finished = ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() { - public void run() { + ProgressManager.getInstance().run(new Task.Backgroundable(project,HgVcsMessages.message("hg4idea.clone.test.progress", testURL), true) { + @Override + public void run(@NotNull ProgressIndicator indicator) { testResult = testRepository(project, testURL); + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + if (testResult) { + Messages.showInfoMessage(testButton, HgVcsMessages.message("hg4idea.clone.test.success.message", testURL), + HgVcsMessages.message("hg4idea.clone.test.success")); + } + updateCloneButton(); + } + }); } - }, HgVcsMessages.message("hg4idea.clone.test.progress", testURL), true, project, clonePanel); - - if (!finished) { - return; - } - - if (testResult) { - Messages.showInfoMessage(testButton, HgVcsMessages.message("hg4idea.clone.test.success.message", testURL), - HgVcsMessages.message("hg4idea.clone.test.success")); - } - updateCloneButton(); + }); } }); diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/ui/HgIntegrateDialog.java b/plugins/hg4idea/src/org/zmlx/hg4idea/ui/HgIntegrateDialog.java index 004befd0d5cc..3d7cc84e25e8 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/ui/HgIntegrateDialog.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/ui/HgIntegrateDialog.java @@ -12,11 +12,13 @@ // limitations under the License. package org.zmlx.hg4idea.ui; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.options.Configurable; import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.project.Project; import com.intellij.openapi.vcs.FilePath; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.Consumer; import org.jetbrains.annotations.Nls; import org.zmlx.hg4idea.HgRevisionNumber; import org.zmlx.hg4idea.HgVcsMessages; @@ -135,35 +137,48 @@ public class HgIntegrateDialog implements Configurable { } private void loadBranches(VirtualFile root) { - List branches = new HgTagBranchCommand(project, root).listBranches(); - branchSelector.setModel(new DefaultComboBoxModel(branches.toArray())); + new HgTagBranchCommand(project, root).listBranches(new Consumer>() { + @Override + public void consume(List branches) { + branchSelector.setModel(new DefaultComboBoxModel(branches.toArray())); + } + }); } private void loadTags(VirtualFile root) { - List tags = new HgTagBranchCommand(project, root).listTags(); - tagSelector.setModel(new DefaultComboBoxModel(tags.toArray())); + new HgTagBranchCommand(project, root).listTags(new Consumer>() { + @Override + public void consume(List tags) { + tagSelector.setModel(new DefaultComboBoxModel(tags.toArray())); + } + }); } - private void loadHeads(VirtualFile root) { - List heads = new HgHeadsCommand(project, root).execute(); - if (heads.size() != 2) { - disableOtherHeadsChoice(); - return; - } + private void loadHeads(final VirtualFile root) { + ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { + @Override + public void run() { + final List heads = new HgHeadsCommand(project, root).execute(); + if (heads.size() != 2) { + disableOtherHeadsChoice(); + return; + } - otherHeadRadioButton.setVisible(true); - otherHeadLabel.setVisible(true); + otherHeadRadioButton.setVisible(true); + otherHeadLabel.setVisible(true); - HgRevisionNumber currentParent = new HgWorkingCopyRevisionsCommand(project).identify(root); - heads.remove(currentParent); + HgRevisionNumber currentParent = new HgWorkingCopyRevisionsCommand(project).identify(root); + heads.remove(currentParent); - if (heads.size() == 1) { - otherHead = heads.get(0); - otherHeadLabel.setText(HgVcsMessages.message("hg4idea.integrate.other.head", otherHead.asString())); - } else { - //apparently we are not at one of the heads - disableOtherHeadsChoice(); - } + if (heads.size() == 1) { + otherHead = heads.get(0); + otherHeadLabel.setText(HgVcsMessages.message("hg4idea.integrate.other.head", otherHead.asString())); + } else { + //apparently we are not at one of the heads + disableOtherHeadsChoice(); + } + } + }); } private void disableOtherHeadsChoice() { diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/ui/HgPullDialog.java b/plugins/hg4idea/src/org/zmlx/hg4idea/ui/HgPullDialog.java index 50a2d8d4bd47..2e70e4ab26b9 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/ui/HgPullDialog.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/ui/HgPullDialog.java @@ -12,6 +12,7 @@ // limitations under the License. package org.zmlx.hg4idea.ui; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.vfs.VirtualFile; @@ -82,11 +83,16 @@ public class HgPullDialog extends DialogWrapper { } private void onChangeRepository() { - VirtualFile repo = hgRepositorySelector.getRepository(); - HgShowConfigCommand configCommand = new HgShowConfigCommand(project); - String defaultPath = configCommand.getDefaultPath(repo); - sourceTxt.setText(defaultPath); - onChangePullSource(); + ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { + @Override + public void run() { + VirtualFile repo = hgRepositorySelector.getRepository(); + HgShowConfigCommand configCommand = new HgShowConfigCommand(project); + String defaultPath = configCommand.getDefaultPath(repo); + sourceTxt.setText(defaultPath); + onChangePullSource(); + } + }); } private void onChangePullSource() { diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/ui/HgPushDialog.java b/plugins/hg4idea/src/org/zmlx/hg4idea/ui/HgPushDialog.java index 425f2fe95a98..941624d8c269 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/ui/HgPushDialog.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/ui/HgPushDialog.java @@ -12,9 +12,12 @@ // limitations under the License. package org.zmlx.hg4idea.ui; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.Consumer; import org.apache.commons.lang.StringUtils; import org.jetbrains.annotations.Nullable; import org.zmlx.hg4idea.HgVcsMessages; @@ -103,17 +106,36 @@ public class HgPushDialog extends DialogWrapper { } private void updateRepository() { - final VirtualFile repo = hgRepositorySelectorComponent.getRepository(); - final HgShowConfigCommand configCommand = new HgShowConfigCommand(myProject); - final String defaultPath = configCommand.getDefaultPushPath(repo); - repositoryTxt.setText(defaultPath); - loadBranches(repo); - update(); + ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { + @Override + public void run() { + final VirtualFile repo = hgRepositorySelectorComponent.getRepository(); + final HgShowConfigCommand configCommand = new HgShowConfigCommand(myProject); + final String defaultPath = configCommand.getDefaultPushPath(repo); + loadBranches(repo); + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + repositoryTxt.setText(defaultPath); + update(); + } + }, ModalityState.stateForComponent(getRootPane())); + } + }); } private void loadBranches(VirtualFile root) { - final List branches = new HgTagBranchCommand(myProject, root).listBranches(); - branchComboBox.setModel(new DefaultComboBoxModel(branches.toArray())); + new HgTagBranchCommand(myProject, root).listBranches(new Consumer>() { + @Override + public void consume(final List branches) { + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + branchComboBox.setModel(new DefaultComboBoxModel(branches.toArray())); + } + }, ModalityState.stateForComponent(getRootPane())); + } + }); } private void update() { diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/ui/HgRunConflictResolverDialog.java b/plugins/hg4idea/src/org/zmlx/hg4idea/ui/HgRunConflictResolverDialog.java index 7cb564cd49f8..94125cfceb67 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/ui/HgRunConflictResolverDialog.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/ui/HgRunConflictResolverDialog.java @@ -15,6 +15,7 @@ package org.zmlx.hg4idea.ui; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.Consumer; import org.zmlx.hg4idea.HgFile; import org.zmlx.hg4idea.command.HgResolveCommand; import org.zmlx.hg4idea.command.HgResolveStatusEnum; @@ -61,18 +62,22 @@ public class HgRunConflictResolverDialog extends DialogWrapper { private void onChangeRepository() { VirtualFile repo = repositorySelector.getRepository(); HgResolveCommand command = new HgResolveCommand(project); - Map status = command.list(repo); - DefaultListModel model = new DefaultListModel(); - for (Map.Entry entry : status.entrySet()) { - if (entry.getValue() == HgResolveStatusEnum.UNRESOLVED) { - model.addElement(entry.getKey().getRelativePath()); + command.list(repo, new Consumer>() { + @Override + public void consume(Map status) { + DefaultListModel model = new DefaultListModel(); + for (Map.Entry entry : status.entrySet()) { + if (entry.getValue() == HgResolveStatusEnum.UNRESOLVED) { + model.addElement(entry.getKey().getRelativePath()); + } + } + setOKActionEnabled(!model.isEmpty()); + if (model.isEmpty()) { + model.addElement("No conflicts to resolve"); + } + conflictsList.setModel(model); } - } - setOKActionEnabled(!model.isEmpty()); - if (model.isEmpty()) { - model.addElement("No conflicts to resolve"); - } - conflictsList.setModel(model); + }); } } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/ui/HgSwitchDialog.java b/plugins/hg4idea/src/org/zmlx/hg4idea/ui/HgSwitchDialog.java index 769947fd3ab4..6f6a1a3ba9bd 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/ui/HgSwitchDialog.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/ui/HgSwitchDialog.java @@ -15,6 +15,7 @@ package org.zmlx.hg4idea.ui; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.Consumer; import org.zmlx.hg4idea.command.HgTagBranch; import org.zmlx.hg4idea.command.HgTagBranchCommand; @@ -115,13 +116,21 @@ public class HgSwitchDialog extends DialogWrapper { } private void loadBranches(VirtualFile root) { - List branches = new HgTagBranchCommand(project, root).listBranches(); - branchSelector.setModel(new DefaultComboBoxModel(branches.toArray())); + new HgTagBranchCommand(project, root).listBranches(new Consumer>() { + @Override + public void consume(List branches) { + branchSelector.setModel(new DefaultComboBoxModel(branches.toArray())); + } + }); } private void loadTags(VirtualFile root) { - List tags = new HgTagBranchCommand(project, root).listTags(); - tagSelector.setModel(new DefaultComboBoxModel(tags.toArray())); + new HgTagBranchCommand(project, root).listTags(new Consumer>() { + @Override + public void consume(List tags) { + tagSelector.setModel(new DefaultComboBoxModel(tags.toArray())); + } + }); } private boolean validateOptions() { diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/ui/HgUsernamePasswordDialog.java b/plugins/hg4idea/src/org/zmlx/hg4idea/ui/HgUsernamePasswordDialog.java index 8980b31f7a3c..9ba12fd92715 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/ui/HgUsernamePasswordDialog.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/ui/HgUsernamePasswordDialog.java @@ -25,9 +25,9 @@ public class HgUsernamePasswordDialog extends DialogWrapper { public HgUsernamePasswordDialog(Project project, String url, String login, String password) { super(project, false); - setTitle(HgVcsMessages.message("hgidea.dialog.login.password.required")); - final String desc = (url == null ? null : "Login to " + url); - authPanel = new AuthenticationPanel(desc, login, password, false); + setTitle(HgVcsMessages.message("hg4idea.dialog.login.password.required")); + authPanel = new AuthenticationPanel(HgVcsMessages.message("hg4idea.dialog.login.description", url), login, password, + !StringUtils.isBlank(password)); init(); } @@ -52,4 +52,4 @@ public class HgUsernamePasswordDialog extends DialogWrapper { return authPanel.isRememberPassword(); } -} \ No newline at end of file +} diff --git a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgPushTest.java b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgPushTest.java index 8423b3693918..d8ec05cedb9d 100644 --- a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgPushTest.java +++ b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgPushTest.java @@ -17,11 +17,9 @@ package org.zmlx.hg4idea.test; import com.intellij.openapi.vfs.VirtualFile; import org.testng.annotations.Test; -import org.zmlx.hg4idea.command.HgCommandResult; -import org.zmlx.hg4idea.command.HgErrorUtil; import org.zmlx.hg4idea.command.HgPushCommand; +import org.zmlx.hg4idea.execution.HgCommandResultHandler; -import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; /** @@ -59,9 +57,7 @@ public class HgPushTest extends HgCollaborativeTest { myChangeListManager.commitFiles(vf); final HgPushCommand command = new HgPushCommand(myProject, myRepo.getDir(), myParentRepo.getDir().getUrl()); - final HgCommandResult result = command.execute(); - assertNotNull(result); - assertFalse(HgErrorUtil.isAbort(result)); + command.execute(HgCommandResultHandler.DUMB); myParentRepo.update(); assertNotNull(myParentRepo.getDir().findChild(AFILE)); diff --git a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgUpdateTest.java b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgUpdateTest.java index 9bcae7a18daa..0ffdce6dc608 100644 --- a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgUpdateTest.java +++ b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgUpdateTest.java @@ -19,10 +19,10 @@ import com.intellij.openapi.vcs.update.UpdatedFiles; import com.intellij.openapi.vfs.VirtualFile; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import org.zmlx.hg4idea.HgChange; import org.zmlx.hg4idea.HgFile; +import org.zmlx.hg4idea.HgFileStatusEnum; import org.zmlx.hg4idea.HgRevisionNumber; -import org.zmlx.hg4idea.command.HgChange; -import org.zmlx.hg4idea.command.HgFileStatusEnum; import org.zmlx.hg4idea.command.HgHeadsCommand; import org.zmlx.hg4idea.command.HgIncomingCommand; import org.zmlx.hg4idea.command.HgParentsCommand; @@ -70,7 +70,7 @@ public class HgUpdateTest extends HgCollaborativeTest { HgPullCommand pull = new HgPullCommand(myProject, projectRepoVirtualFile); pull.setSource(new HgShowConfigCommand(myProject).getDefaultPath(projectRepoVirtualFile)); pull.setUpdate(false); - pull.execute(); + pull.execute(null); assertEquals( determineNumberOfIncomingChanges( projectRepo ), 0, "The update operation should have pulled the incoming changes from the default repository." ); diff --git a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgUrlTestCase.java b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgUrlTestCase.java deleted file mode 100644 index ed7f7086aaef..000000000000 --- a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgUrlTestCase.java +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2008-2010 Victor Iacoban -// -// 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.zmlx.hg4idea.test; - -import org.testng.Assert; -import org.testng.annotations.Test; -import org.zmlx.hg4idea.command.HgUrl; - -public class HgUrlTestCase { - - @Test - public void testHttp() throws Exception { - HgUrl url = new HgUrl("http://vasea:pupkin@host.somewhere.com:8081/hg"); - Assert.assertEquals("vasea", url.getUsername()); - Assert.assertEquals("pupkin", url.getPassword()); - Assert.assertTrue(url.supportsAuthentication()); - - url = new HgUrl("http://:pupkin@host.somewhere.com:8081/hg"); - Assert.assertNull(url.getUsername()); - Assert.assertEquals("pupkin", url.getPassword()); - Assert.assertTrue(url.supportsAuthentication()); - - url = new HgUrl("http://vasea@host.somewhere.com:8081/hg"); - Assert.assertEquals("vasea", url.getUsername()); - Assert.assertNull(url.getPassword()); - Assert.assertTrue(url.supportsAuthentication()); - - url = new HgUrl("http://vasea:@host.somewhere.com:8081/hg"); - Assert.assertEquals("vasea", url.getUsername()); - Assert.assertNull(url.getPassword()); - Assert.assertTrue(url.supportsAuthentication()); - } - - @Test - public void testHttps() throws Exception { - HgUrl url = new HgUrl("https://vasea:pupkin@host.somewhere.com:8081/hg"); - Assert.assertEquals("vasea", url.getUsername()); - Assert.assertEquals("pupkin", url.getPassword()); - Assert.assertTrue(url.supportsAuthentication()); - - url = new HgUrl("https://host.somewhere.com:8081/hg"); - Assert.assertNull(url.getUsername()); - Assert.assertNull(url.getPassword()); - Assert.assertTrue(url.supportsAuthentication()); - } - - @Test - public void testSsh() throws Exception { - HgUrl url = new HgUrl("ssh://vasea:pupkin@host.somewhere.com:8081/hg"); - Assert.assertEquals("vasea", url.getUsername()); - Assert.assertEquals("pupkin", url.getPassword()); - Assert.assertTrue(url.supportsAuthentication()); - - url = new HgUrl("ssh://vasea@host.somewhere.com:8081/hg"); - Assert.assertEquals("vasea", url.getUsername()); - Assert.assertNull(url.getPassword()); - Assert.assertTrue(url.supportsAuthentication()); - } - - @Test - public void testFile() throws Exception { - HgUrl url = new HgUrl("/home/vasea/pupkin"); - Assert.assertFalse(url.supportsAuthentication()); - - url = new HgUrl("./vasea/pupkin"); - Assert.assertFalse(url.supportsAuthentication()); - - url = new HgUrl("../vasea/pupkin"); - Assert.assertFalse(url.supportsAuthentication()); - - url = new HgUrl("file:///vasea/pupkin"); - Assert.assertFalse(url.supportsAuthentication()); - } - -} diff --git a/plugins/junit/src/com/intellij/execution/junit2/configuration/JUnitConfigurable.java b/plugins/junit/src/com/intellij/execution/junit2/configuration/JUnitConfigurable.java index 6689822ff7a1..60d8004c9ac0 100644 --- a/plugins/junit/src/com/intellij/execution/junit2/configuration/JUnitConfigurable.java +++ b/plugins/junit/src/com/intellij/execution/junit2/configuration/JUnitConfigurable.java @@ -96,7 +96,8 @@ public class JUnitConfigurable extends SettingsEditor { private JComboBox myForkCb; @NonNls private static final String NONE = "none"; @NonNls private static final String METHOD = "method"; - private static final String[] FORK_MODE_ALL = {NONE, METHOD, "class"}; + @NonNls private static final String KLASS = "class"; + private static final String[] FORK_MODE_ALL = {NONE, METHOD, KLASS}; private static final String[] FORK_MODE = {NONE, METHOD}; public JUnitConfigurable(final Project project) { @@ -203,6 +204,10 @@ public class JUnitConfigurable extends SettingsEditor { } private void changePanel () { + String selectedItem = (String)myForkCb.getSelectedItem(); + if (selectedItem == null) { + selectedItem = NONE; + } if (myAllInPackageButton.isSelected()) { myPackagePanel.setVisible(true); myPattern.setVisible(false); @@ -210,7 +215,7 @@ public class JUnitConfigurable extends SettingsEditor { myMethod.setVisible(false); myForkCb.setEnabled(true); myForkCb.setModel(new DefaultComboBoxModel(FORK_MODE_ALL)); - myForkCb.setSelectedItem(NONE); + myForkCb.setSelectedItem(selectedItem); } else if (myClassButton.isSelected()){ myPackagePanel.setVisible(false); @@ -219,7 +224,7 @@ public class JUnitConfigurable extends SettingsEditor { myMethod.setVisible(false); myForkCb.setEnabled(true); myForkCb.setModel(new DefaultComboBoxModel(FORK_MODE)); - myForkCb.setSelectedItem(NONE); + myForkCb.setSelectedItem(selectedItem != KLASS ? selectedItem : METHOD); } else if (myTestMethodButton.isSelected()){ myPackagePanel.setVisible(false); @@ -235,7 +240,7 @@ public class JUnitConfigurable extends SettingsEditor { myMethod.setVisible(false); myForkCb.setEnabled(true); myForkCb.setModel(new DefaultComboBoxModel(FORK_MODE_ALL)); - myForkCb.setSelectedItem(NONE); + myForkCb.setSelectedItem(selectedItem); } } diff --git a/plugins/relaxng/src/org/intellij/plugins/relaxNG/model/resolve/DefinitionResolver.java b/plugins/relaxng/src/org/intellij/plugins/relaxNG/model/resolve/DefinitionResolver.java index 5ae1aed28033..13c0a8a5b8f6 100644 --- a/plugins/relaxng/src/org/intellij/plugins/relaxNG/model/resolve/DefinitionResolver.java +++ b/plugins/relaxng/src/org/intellij/plugins/relaxNG/model/resolve/DefinitionResolver.java @@ -51,8 +51,8 @@ public class DefinitionResolver extends CommonElement.Visitor implements private static final Key>>> KEY = Key.create("CACHED_DEFINES"); - private THashSet myVisitedFiles; - private Map> myDefines; + private static final ThreadLocal> myVisitedFiles = new ThreadLocal>(); + private static final ThreadLocal>> myDefines = new ThreadLocal>>(); private final Grammar myScope; @@ -65,11 +65,11 @@ public class DefinitionResolver extends CommonElement.Visitor implements include.acceptChildren(this); final PsiFile value = include.getInclude(); - if (myVisitedFiles == null) { + if (myVisitedFiles.get() == null) { //noinspection unchecked - myVisitedFiles = new THashSet(TObjectHashingStrategy.IDENTITY); + myVisitedFiles.set(new THashSet(TObjectHashingStrategy.IDENTITY)); } - if (value != null && myVisitedFiles.add(value)) { + if (value != null && myVisitedFiles.get().add(value)) { if (value instanceof RncFile) { final Grammar grammar = ((RncFile)value).getGrammar(); if (grammar != null) { @@ -92,7 +92,7 @@ public class DefinitionResolver extends CommonElement.Visitor implements @Override public void visitDefine(Define def) { - ContainerUtil.getOrCreate(myDefines, def.getName(), this).add(def); + ContainerUtil.getOrCreate(myDefines.get(), def.getName(), this).add(def); } public void visitPattern(Pattern pattern) { @@ -110,7 +110,7 @@ public class DefinitionResolver extends CommonElement.Visitor implements } public Result>> compute() { - myDefines = new HashMap>(); + myDefines.set(new HashMap>()); myScope.acceptChildren(this); @@ -121,15 +121,15 @@ public class DefinitionResolver extends CommonElement.Visitor implements } final PsiFile file = psiElement.getContainingFile(); - if (myVisitedFiles != null) { - myVisitedFiles.add(file); - return Result.create(myDefines, myVisitedFiles.toArray()); + if (myVisitedFiles.get() != null) { + myVisitedFiles.get().add(file); + return Result.create(myDefines.get(), myVisitedFiles.get().toArray()); } else { - return Result.create(myDefines, file); + return Result.create(myDefines.get(), file); } } finally { - myVisitedFiles = null; - myDefines = null; + myVisitedFiles.set(null); + myDefines.set(null); } } diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/tokenizer/PsiIdentifierOwnerTokenizer.java b/plugins/spellchecker/src/com/intellij/spellchecker/tokenizer/PsiIdentifierOwnerTokenizer.java index f674e6900dd6..84b81aa20fc5 100644 --- a/plugins/spellchecker/src/com/intellij/spellchecker/tokenizer/PsiIdentifierOwnerTokenizer.java +++ b/plugins/spellchecker/src/com/intellij/spellchecker/tokenizer/PsiIdentifierOwnerTokenizer.java @@ -15,6 +15,7 @@ */ package com.intellij.spellchecker.tokenizer; +import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiNameIdentifierOwner; import com.intellij.psi.util.PsiTreeUtil; @@ -33,10 +34,13 @@ public class PsiIdentifierOwnerTokenizer extends Tokenizer(parent, identifier.getText(), true, offset, SplitterFactory.getInstance().getIdentifierSplitter())}; } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/QuickMerge.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/QuickMerge.java index caa29b8451b7..69c6e54f2707 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/QuickMerge.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/QuickMerge.java @@ -15,6 +15,7 @@ */ package org.jetbrains.idea.svn.dialogs; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.progress.ProgressIndicator; @@ -88,7 +89,7 @@ public class QuickMerge { myWcInfo = wcInfo; myTitle = "Merge from " + myBranchName; - myContinuation = new Continuation(myProject, true); + myContinuation = Continuation.createFragmented(myProject, true); } private class SourceUrlCorrection extends TaskDescriptor { @@ -157,6 +158,8 @@ public class QuickMerge { @CalledInAwt public void execute() { + FileDocumentManager.getInstance().saveAllDocuments(); + final List tasks = new LinkedList(); tasks.add(new MyInitChecks()); tasks.add(new SourceUrlCorrection()); @@ -665,7 +668,12 @@ public class QuickMerge { for (String name : map.keySet()) { try { final Collection changes = map.get(name); - FileDocumentManager.getInstance().saveAllDocuments(); + ApplicationManager.getApplication().invokeAndWait(new Runnable() { + @Override + public void run() { + FileDocumentManager.getInstance().saveAllDocuments(); + } + }, ModalityState.NON_MODAL); ShelveChangesManager.getInstance(myProject).shelveChanges(changes, myIntersection.getComment(name) + " (auto shelve before merge)"); session.addAllFiles(ChangesUtil.getFilesFromChanges(changes)); } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RunOrContinuation.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RunOrContinuation.java index 715ab69583ee..a89240ff7b82 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RunOrContinuation.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RunOrContinuation.java @@ -82,7 +82,7 @@ public abstract class RunOrContinuation { @CalledInAwt public void execute() { - new Continuation(myProject, true).run(getTask()); + Continuation.createFragmented(myProject, true).run(getTask()); } /*@CalledInAwt diff --git a/resources-en/src/tips/tips.xml b/resources-en/src/tips/tips.xml index 03f1bd26c99f..303b31ef3eec 100644 --- a/resources-en/src/tips/tips.xml +++ b/resources-en/src/tips/tips.xml @@ -139,4 +139,5 @@ + diff --git a/xml/dom-openapi/src/com/intellij/util/xml/converters/values/NumberValueConverter.java b/xml/dom-openapi/src/com/intellij/util/xml/converters/values/NumberValueConverter.java index 7445d9a1559b..7aa3b9d33aef 100644 --- a/xml/dom-openapi/src/com/intellij/util/xml/converters/values/NumberValueConverter.java +++ b/xml/dom-openapi/src/com/intellij/util/xml/converters/values/NumberValueConverter.java @@ -19,14 +19,17 @@ package com.intellij.util.xml.converters.values; import com.intellij.util.xml.ConvertContext; import com.intellij.util.xml.Converter; import com.intellij.util.xml.DomBundle; +import com.intellij.util.xml.ResolvingConverter; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.math.BigDecimal; import java.math.BigInteger; +import java.util.Collection; +import java.util.Collections; -public class NumberValueConverter extends Converter { +public class NumberValueConverter extends ResolvingConverter { private final Class myNumberClass; private final boolean myAllowEmpty; @@ -57,6 +60,12 @@ public class NumberValueConverter extends Converter { DomBundle.message("value.converter.format.exception", s, myNumberClass.getName()); } + @NotNull + @Override + public Collection getVariants(ConvertContext context) { + return Collections.emptySet(); + } + @Nullable public static Number parseNumber(@NotNull String text, @NotNull Class targetClass) { try { diff --git a/xml/impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/URLReference.java b/xml/impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/URLReference.java index 24f45843be90..0278e76e25d6 100644 --- a/xml/impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/URLReference.java +++ b/xml/impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/URLReference.java @@ -39,12 +39,12 @@ public class URLReference implements PsiReference, QuickFixProvider, EmptyResolv @NonNls private static final String TARGET_NAMESPACE_ATTR_NAME = "targetNamespace"; private final PsiElement myElement; - private TextRange myRange; - private boolean mySoft; + private final TextRange myRange; + private final boolean mySoft; private boolean myIncorrectResourceMapped; public URLReference(PsiElement element) { - myElement = element; + this(element, null, false); } public URLReference(PsiElement element, @Nullable TextRange range, boolean soft) { @@ -69,10 +69,9 @@ public class URLReference implements PsiReference, QuickFixProvider, EmptyResolv if (canonicalText.length() == 0) { final XmlAttribute attr = PsiTreeUtil.getParentOfType(getElement(), XmlAttribute.class); - if (( attr != null && + if (attr != null && attr.isNamespaceDeclaration() && - attr.getNamespacePrefix().length() == 0 - ) || + attr.getNamespacePrefix().length() == 0 || ExternalResourceManagerEx.getInstanceEx().isIgnoredResource(canonicalText) ) { // Namespaces in XML 1.0 2nd edition, Section 6.2, last paragraph