diff --git a/.idea/libraries/Netty.xml b/.idea/libraries/Netty.xml index aafde743ce14..c094259951d9 100644 --- a/.idea/libraries/Netty.xml +++ b/.idea/libraries/Netty.xml @@ -1,11 +1,11 @@ - + - + \ No newline at end of file diff --git a/.idea/libraries/miglayout_swing.xml b/.idea/libraries/miglayout_swing.xml new file mode 100644 index 000000000000..5eced334efaa --- /dev/null +++ b/.idea/libraries/miglayout_swing.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/community-resources/src/idea/IdeaApplicationInfo.xml b/community-resources/src/idea/IdeaApplicationInfo.xml index 80bc93534f4f..035e49f13af7 100644 --- a/community-resources/src/idea/IdeaApplicationInfo.xml +++ b/community-resources/src/idea/IdeaApplicationInfo.xml @@ -1,5 +1,5 @@ - + diff --git a/java/compiler/impl/src/com/intellij/compiler/CompileServerManager.java b/java/compiler/impl/src/com/intellij/compiler/CompileServerManager.java index 1bdde25fc857..4457a9d94f92 100644 --- a/java/compiler/impl/src/com/intellij/compiler/CompileServerManager.java +++ b/java/compiler/impl/src/com/intellij/compiler/CompileServerManager.java @@ -76,10 +76,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.*; -import java.util.concurrent.Future; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.RunnableFuture; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -91,26 +88,21 @@ public class CompileServerManager implements ApplicationComponent{ private static final String COMPILE_SERVER_SYSTEM_ROOT = "compile-server"; private static final String LOGGER_CONFIG = "log.xml"; private static final String DEFAULT_LOGGER_CONFIG = "defaultLogConfig.xml"; - private volatile ServerWrapper myProcessHandler; + private volatile OSProcessHandler myProcessHandler; private final File mySystemDirectory; @Nullable private volatile CompileServerClient myClient; - private final SequentialTaskExecutor myTaskExecutor = new SequentialTaskExecutor(new AsyncTaskExecutor() { - @Override - public void submit(Runnable runnable) { - ApplicationManager.getApplication().executeOnPooledThread(runnable); - } - }); private final ProjectManager myProjectManager; private static final int MAKE_TRIGGER_DELAY = 5 * 1000 /*5 seconds*/; private final Map myAutomakeFutures = new HashMap(); private final CompileServerClasspathManager myClasspathManager = new CompileServerClasspathManager(); - private final AsyncTaskExecutor myAsyncExec = new AsyncTaskExecutor() { + private final Executor myPooledThreadExecutor = new Executor() { @Override - public void submit(Runnable runnable) { - ApplicationManager.getApplication().executeOnPooledThread(runnable); + public void execute(Runnable command) { + ApplicationManager.getApplication().executeOnPooledThread(command); } }; + private final SequentialTaskExecutor myTaskExecutor = new SequentialTaskExecutor(myPooledThreadExecutor); public CompileServerManager(final ProjectManager projectManager) { myProjectManager = projectManager; @@ -335,7 +327,7 @@ public class CompileServerManager implements ApplicationComponent{ public RequestFuture submitCompilationTask(final Project project, final boolean isRebuild, final boolean isMake, final Collection modules, final Collection artifacts, final Collection paths, - final Map _userData, final JpsServerResponseHandler handler) { + final Map userData, final JpsServerResponseHandler handler) { final String projectId = getProjectPath(project); final Ref futureRef = new Ref(null); final RunnableFuture future = myTaskExecutor.submit(new Runnable() { @@ -344,16 +336,9 @@ public class CompileServerManager implements ApplicationComponent{ try { final CompileServerClient client = ensureServerRunningAndClientConnected(true); if (client != null) { - final Map userData = new LinkedHashMap(); - if (!isRebuild) { //todo pass user data on rebuild as well? - userData.putAll(_userData); - } - if (Registry.is("compiler.server.use.external.javac.process")) { - userData.put(GlobalOptions.USE_EXTERNAL_JAVAC_OPTION, "true"); - } final RequestFuture requestFuture = isRebuild ? - client.sendRebuildRequest(projectId, handler, userData) : - client.sendCompileRequest(isMake, projectId, modules, artifacts, paths, userData, handler); + client.sendRebuildRequest(projectId, handler, userData) : + client.sendCompileRequest(isMake, projectId, modules, artifacts, paths, userData, handler); futureRef.set(requestFuture); } else { @@ -397,9 +382,9 @@ public class CompileServerManager implements ApplicationComponent{ // executed in one thread at a time @Nullable private CompileServerClient ensureServerRunningAndClientConnected(boolean forceRestart) throws Throwable { - final ServerWrapper ph = myProcessHandler; + final OSProcessHandler ph = myProcessHandler; final CompileServerClient cl = myClient; - final boolean processNotRunning = ph == null || ph.isDead(); + final boolean processNotRunning = ph == null || ph.isProcessTerminated() || ph.isProcessTerminating(); final boolean clientNotConnected = cl == null || !cl.isConnected(); if (processNotRunning || clientNotConnected) { @@ -412,105 +397,89 @@ public class CompileServerManager implements ApplicationComponent{ return null; } - final File workDirectory = new File(mySystemDirectory, COMPILE_SERVER_SYSTEM_ROOT); - workDirectory.mkdirs(); - ensureLogConfigExists(workDirectory); - final int port = NetUtils.findAvailableSocketPort(); - final long serverPingInterval = Registry.intValue("compiler.server.ping.interval", -1) * 1000L; - ServerWrapper wrapper = Registry.is("compiler.server.in.process") ? launchServerThread(workDirectory, port) : launchServerProcess(port, serverPingInterval, workDirectory); + final long serverPingInterval = Registry.intValue("compiler.server.ping.interval", -1) * 1000L; + final Process process = launchServer(port, serverPingInterval); - CompileServerClient client = new CompileServerClient(serverPingInterval, myAsyncExec); + final OSProcessHandler processHandler = new OSProcessHandler(process, null) { + @Override + protected boolean shouldDestroyProcessRecursively() { + return true; + } + }; + final StringBuilder serverStartMessage = new StringBuilder(); + final Semaphore semaphore = new Semaphore(); + semaphore.down(); + processHandler.addProcessListener(new ProcessAdapter() { + @Override + public void onTextAvailable(ProcessEvent event, Key outputType) { + // re-translate server's output to idea.log + final String text = event.getText(); + if (!StringUtil.isEmpty(text)) { + LOG.info("COMPILE_SERVER [" +outputType.toString() +"]: "+ text.trim()); + } + } + }); + processHandler.addProcessListener(new ProcessAdapter() { + @Override + public void processTerminated(ProcessEvent event) { + try { + processHandler.removeProcessListener(this); + } + finally { + semaphore.up(); + } + } + + @Override + public void onTextAvailable(ProcessEvent event, Key outputType) { + if (outputType == ProcessOutputTypes.STDERR) { + try { + final String text = event.getText(); + if (text != null) { + if (text.contains(Server.SERVER_SUCCESS_START_MESSAGE) || text.contains(Server.SERVER_ERROR_START_MESSAGE)) { + processHandler.removeProcessListener(this); + } + if (serverStartMessage.length() > 0) { + serverStartMessage.append("\n"); + } + serverStartMessage.append(text); + } + } + finally { + semaphore.up(); + } + } + } + }); + processHandler.startNotify(); + semaphore.waitFor(); + + final String startupMsg = serverStartMessage.toString(); + if (!startupMsg.contains(Server.SERVER_SUCCESS_START_MESSAGE)) { + throw new Exception("Server startup failed: " + startupMsg); + } + + CompileServerClient client = new CompileServerClient(serverPingInterval, myPooledThreadExecutor); boolean connected = false; try { connected = client.connect(NetUtils.getLocalHostString(), port); if (connected) { final RequestFuture setupFuture = sendSetupRequest(client); setupFuture.waitFor(); - myProcessHandler = wrapper; + myProcessHandler = processHandler; myClient = client; } } finally { if (!connected) { - shutdownServer(cl, wrapper); + shutdownServer(cl, processHandler); } } } return myClient; } - private static ServerWrapper launchServerThread(final File workDirectory, final int port) { - final Server server = new Server(workDirectory, Registry.is("compiler.server.use.memory.temp.cache")); - //todo hostname - server.start(port); - return new ServerWrapper(null, server); - } - - private ServerWrapper launchServerProcess(int port, long serverPingInterval, File workDirectory) throws Exception { - final Process process = launchServer(port, serverPingInterval, workDirectory); - - final OSProcessHandler processHandler = new OSProcessHandler(process, null) { - @Override - protected boolean shouldDestroyProcessRecursively() { - return true; - } - }; - final StringBuilder serverStartMessage = new StringBuilder(); - final Semaphore semaphore = new Semaphore(); - semaphore.down(); - processHandler.addProcessListener(new ProcessAdapter() { - @Override - public void onTextAvailable(ProcessEvent event, Key outputType) { - // re-translate server's output to idea.log - final String text = event.getText(); - if (!StringUtil.isEmpty(text)) { - LOG.info("COMPILE_SERVER [" +outputType.toString() +"]: "+ text.trim()); - } - } - }); - processHandler.addProcessListener(new ProcessAdapter() { - @Override - public void processTerminated(ProcessEvent event) { - try { - processHandler.removeProcessListener(this); - } - finally { - semaphore.up(); - } - } - - @Override - public void onTextAvailable(ProcessEvent event, Key outputType) { - if (outputType == ProcessOutputTypes.STDERR) { - try { - final String text = event.getText(); - if (text != null) { - if (text.contains(Server.SERVER_SUCCESS_START_MESSAGE) || text.contains(Server.SERVER_ERROR_START_MESSAGE)) { - processHandler.removeProcessListener(this); - } - if (serverStartMessage.length() > 0) { - serverStartMessage.append("\n"); - } - serverStartMessage.append(text); - } - } - finally { - semaphore.up(); - } - } - } - }); - processHandler.startNotify(); - semaphore.waitFor(); - - final String startupMsg = serverStartMessage.toString(); - if (!startupMsg.contains(Server.SERVER_SUCCESS_START_MESSAGE)) { - throw new Exception("Server startup failed: " + startupMsg); - } - return new ServerWrapper(processHandler, null); - } - private static RequestFuture sendSetupRequest(final @NotNull CompileServerClient client) throws Exception { final Map data = new HashMap(); @@ -593,7 +562,7 @@ public class CompileServerManager implements ApplicationComponent{ // commandLine.add((launcherUsed? "-J" : "") + "-D" + CharsetToolkit.FILE_ENCODING_PROPERTY + "=" + CharsetToolkit.getDefaultSystemCharset().name()); //} - private Process launchServer(final int port, long pingInterval, File workDirectory) throws ExecutionException { + private Process launchServer(final int port, long pingInterval) throws ExecutionException { // validate tools.jar presence final JavaCompiler systemCompiler = ToolProvider.getSystemJavaCompiler(); if (systemCompiler == null) { @@ -642,6 +611,9 @@ public class CompileServerManager implements ApplicationComponent{ if (Registry.is("compiler.server.use.memory.temp.cache")) { cmdLine.addParameter("-D"+ GlobalOptions.USE_MEMORY_TEMP_CACHE_OPTION + "=true"); } + if (Registry.is("compiler.server.use.external.javac.process")) { + cmdLine.addParameter("-D"+ GlobalOptions.USE_EXTERNAL_JAVAC_OPTION + "=true"); + } cmdLine.addParameter("-D"+ GlobalOptions.HOSTNAME_OPTION + "=" + NetUtils.getLocalHostString()); // javac's VM should use the same default locale that IDEA uses in order for javac to print messages in 'correct' language @@ -672,6 +644,10 @@ public class CompileServerManager implements ApplicationComponent{ cmdLine.addParameter(org.jetbrains.jps.server.Server.class.getName()); cmdLine.addParameter(Integer.toString(port)); + final File workDirectory = new File(mySystemDirectory, COMPILE_SERVER_SYSTEM_ROOT); + workDirectory.mkdirs(); + ensureLogConfigExists(workDirectory); + cmdLine.addParameter(FileUtil.toSystemIndependentName(workDirectory.getPath())); cmdLine.setWorkDirectory(workDirectory); @@ -711,7 +687,7 @@ public class CompileServerManager implements ApplicationComponent{ shutdownServer(myClient, myProcessHandler); } - private static void shutdownServer(final CompileServerClient client, final ServerWrapper processHandler) { + private static void shutdownServer(final CompileServerClient client, final OSProcessHandler processHandler) { try { if (client != null && client.isConnected()) { final Future future = client.sendShutdownRequest(); @@ -884,28 +860,4 @@ public class CompileServerManager implements ApplicationComponent{ } } } - - private static class ServerWrapper { - final @Nullable OSProcessHandler myHandler; - final @Nullable Server myServer; - - ServerWrapper(OSProcessHandler handler, Server server) { - myHandler = handler; - myServer = server; - } - - public void destroyProcess() { - if (myHandler != null) { - myHandler.destroyProcess(); - } else if (myServer != null) { - myServer.stop(); - } - } - - public boolean isDead() { - return myHandler != null && (myHandler.isProcessTerminated() || myHandler.isProcessTerminating()) || - myServer != null && myServer.isStopped(); - } - } } - diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactManagerImpl.java b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactManagerImpl.java index 4ee35f67797b..57f4b67dbcbf 100644 --- a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactManagerImpl.java +++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactManagerImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2012 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -284,9 +284,7 @@ public class ArtifactManagerImpl extends ArtifactManager implements ProjectCompo ContainerUtil.addIfNotNull(request, requestsToRemove); } - final LocalFileSystem fileSystem = LocalFileSystem.getInstance(); - fileSystem.removeWatchedRoots(requestsToRemove); - final Set newRequests = fileSystem.addRootsToWatch(toAdd, true); + Set newRequests = LocalFileSystem.getInstance().replaceWatchedRoots(requestsToRemove, toAdd, true); for (LocalFileSystem.WatchRequest request : newRequests) { myWatchedOutputs.put(request.getRootPath(), request); } diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/run/BuildArtifactsBeforeRunTask.java b/java/compiler/impl/src/com/intellij/packaging/impl/run/BuildArtifactsBeforeRunTask.java index 0157fb78953a..d312a9475c69 100644 --- a/java/compiler/impl/src/com/intellij/packaging/impl/run/BuildArtifactsBeforeRunTask.java +++ b/java/compiler/impl/src/com/intellij/packaging/impl/run/BuildArtifactsBeforeRunTask.java @@ -31,13 +31,14 @@ import java.util.List; /** * @author nik */ -public class BuildArtifactsBeforeRunTask extends BeforeRunTask { +public class BuildArtifactsBeforeRunTask extends BeforeRunTask { @NonNls public static final String NAME_ATTRIBUTE = "name"; @NonNls public static final String ARTIFACT_ELEMENT = "artifact"; private List myArtifactPointers = new ArrayList(); private final Project myProject; public BuildArtifactsBeforeRunTask(Project project) { + super(BuildArtifactsBeforeRunTaskProvider.ID); myProject = project; } diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/run/BuildArtifactsBeforeRunTaskProvider.java b/java/compiler/impl/src/com/intellij/packaging/impl/run/BuildArtifactsBeforeRunTaskProvider.java index ee2315e3a046..667bd73ea91e 100644 --- a/java/compiler/impl/src/com/intellij/packaging/impl/run/BuildArtifactsBeforeRunTaskProvider.java +++ b/java/compiler/impl/src/com/intellij/packaging/impl/run/BuildArtifactsBeforeRunTaskProvider.java @@ -15,6 +15,7 @@ */ package com.intellij.packaging.impl.run; +import com.intellij.execution.BeforeRunTask; import com.intellij.execution.BeforeRunTaskProvider; import com.intellij.execution.RunManagerEx; import com.intellij.execution.configurations.RunConfiguration; @@ -33,6 +34,7 @@ import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Ref; import com.intellij.packaging.artifacts.*; +import com.intellij.packaging.impl.artifacts.PlainArtifactType; import com.intellij.packaging.impl.compiler.ArtifactAwareCompiler; import com.intellij.packaging.impl.compiler.ArtifactCompileScope; import com.intellij.packaging.impl.compiler.ArtifactsCompiler; @@ -63,8 +65,8 @@ public class BuildArtifactsBeforeRunTaskProvider extends BeforeRunTaskProvider tasks = runManager.getBeforeRunTasks(configuration, ID); + for (BuildArtifactsBeforeRunTask task : tasks) { final String artifactName = artifact.getName(); final List pointersList = task.getArtifactPointers(); final ArtifactPointer[] pointers = pointersList.toArray(new ArtifactPointer[pointersList.size()]); @@ -83,18 +85,40 @@ public class BuildArtifactsBeforeRunTaskProvider extends BeforeRunTaskProvider pointers = task.getArtifactPointers(); - if (!task.isEnabled() || pointers.isEmpty()) { - return "Build Artifacts"; - } - if (pointers.size() == 1) { - return "Build '" + pointers.get(0).getArtifactName() + "' artifact"; - } - return "Build " + pointers.size() + " artifacts"; + @Override + public Icon getIcon() { + return PlainArtifactType.ARTIFACT_ICON; } - public boolean hasConfigurationButton() { + @Override + public String getName() { + return CompilerBundle.message("build.artifacts.before.run.description.empty"); + } + + @Override + public Icon getTaskIcon(BuildArtifactsBeforeRunTask task) { + List pointers = task.getArtifactPointers(); + if (pointers == null || pointers.isEmpty()) + return getIcon(); + Artifact artifact = pointers.get(0).getArtifact(); + if (artifact == null) + return getIcon(); + return artifact.getArtifactType().getIcon(); + } + + @Override + public String getDescription(BuildArtifactsBeforeRunTask task) { + final List pointers = task.getArtifactPointers(); + if (!task.isEnabled() || pointers.isEmpty()) { + return CompilerBundle.message("build.artifacts.before.run.description.empty"); + } + if (pointers.size() == 1) { + return CompilerBundle.message("build.artifacts.before.run.description.single", pointers.get(0).getArtifactName()); + } + return CompilerBundle.message("build.artifacts.before.run.description.multiple", pointers.size()); + } + + public boolean isConfigurable() { return true; } @@ -115,7 +139,7 @@ public class BuildArtifactsBeforeRunTaskProvider extends BeforeRunTaskProvider result = Ref.create(false); final Semaphore finished = new Semaphore(); @@ -170,27 +203,35 @@ public class BuildArtifactsBeforeRunTaskProvider extends BeforeRunTaskProvider tasks = editor.getStepsBeforeLaunch(); + List myTasks = new ArrayList(); + for (BeforeRunTask task : tasks) { + if (task instanceof BuildArtifactsBeforeRunTask) { + myTasks.add((BuildArtifactsBeforeRunTask)task); + } } - else { - task.removeArtifact(artifact); - if (task.getArtifactPointers().isEmpty()) { - task.setEnabled(false); + for (BuildArtifactsBeforeRunTask task : myTasks) { + if (enable) { + task.addArtifact(artifact); + task.setEnabled(true); + } + else { + task.removeArtifact(artifact); + if (task.getArtifactPointers().isEmpty()) { + task.setEnabled(false); + } } } - editor.updateBeforeRunTaskPanel(ID); } } public static void setBuildArtifactBeforeRun(@NotNull Project project, @NotNull RunConfiguration configuration, @NotNull Artifact artifact) { RunManagerEx runManager = RunManagerEx.getInstanceEx(project); - final BuildArtifactsBeforeRunTask buildArtifactsTask = runManager.getBeforeRunTask(configuration, ID); - if (buildArtifactsTask != null) { - buildArtifactsTask.setEnabled(true); - buildArtifactsTask.addArtifact(artifact); + final List buildArtifactsTasks = runManager.getBeforeRunTasks(configuration, ID); + for (BuildArtifactsBeforeRunTask task : buildArtifactsTasks) { + task.setEnabled(true); + task.addArtifact(artifact); + } } } diff --git a/java/execution/impl/src/com/intellij/compiler/options/CompileStepBeforeRun.java b/java/execution/impl/src/com/intellij/compiler/options/CompileStepBeforeRun.java index 2822a51bac32..4c2a775ccd7e 100644 --- a/java/execution/impl/src/com/intellij/compiler/options/CompileStepBeforeRun.java +++ b/java/execution/impl/src/com/intellij/compiler/options/CompileStepBeforeRun.java @@ -32,12 +32,15 @@ import com.intellij.openapi.compiler.CompilerManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.IconLoader; import com.intellij.openapi.util.Key; import com.intellij.util.concurrency.Semaphore; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import javax.swing.*; + /** * @author spleaner */ @@ -47,6 +50,9 @@ public class CompileStepBeforeRun extends BeforeRunTaskProvider RUN_CONFIGURATION = Key.create("RUN_CONFIGURATION"); @NonNls protected static final String MAKE_PROJECT_ON_RUN_KEY = "makeProjectOnRun"; + + private static final Icon ICON = IconLoader.getIcon("/actions/compile.png"); + private final Project myProject; public CompileStepBeforeRun(@NotNull final Project project) { @@ -57,10 +63,26 @@ public class CompileStepBeforeRun extends BeforeRunTaskProvider { private MakeBeforeRunTask() { + super(ID); setEnabled(true); } } diff --git a/java/java-impl/src/com/intellij/codeInspection/inferNullity/AnnotateTask.java b/java/java-impl/src/com/intellij/codeInspection/inferNullity/AnnotateTask.java new file mode 100644 index 000000000000..3cc23f4140ff --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInspection/inferNullity/AnnotateTask.java @@ -0,0 +1,64 @@ +/* + * Copyright 2000-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.codeInspection.inferNullity; + +import com.intellij.codeInsight.NullableNotNullManager; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.project.Project; +import com.intellij.util.SequentialModalProgressTask; +import com.intellij.util.SequentialTask; + +class AnnotateTask implements SequentialTask { + private final Project myProject; + private final NullityInferrer myInferrer; + private final SequentialModalProgressTask myTask; + private int myCount = 0; + private final int myTotal; + private final NullableNotNullManager myNotNullManager; + + public AnnotateTask(Project project, NullityInferrer inferrer, SequentialModalProgressTask progressTask) { + myProject = project; + myNotNullManager = NullableNotNullManager.getInstance(myProject); + myInferrer = inferrer; + myTask = progressTask; + myTotal = myInferrer.getCount(); + } + + @Override + public void prepare() { + } + + @Override + public boolean isDone() { + return myCount > myTotal - 1; + } + + @Override + public boolean iteration() { + final ProgressIndicator indicator = myTask.getIndicator(); + if (indicator != null) { + indicator.setFraction(((double)myCount) / myTotal); + } + + myInferrer.apply(myCount++, myProject, myNotNullManager); + + return isDone(); + } + + @Override + public void stop() { + } +} \ No newline at end of file diff --git a/java/java-impl/src/com/intellij/codeInspection/inferNullity/InferNullityAnnotationsAction.java b/java/java-impl/src/com/intellij/codeInspection/inferNullity/InferNullityAnnotationsAction.java index e4c214917b06..b3f54b8061d2 100644 --- a/java/java-impl/src/com/intellij/codeInspection/inferNullity/InferNullityAnnotationsAction.java +++ b/java/java-impl/src/com/intellij/codeInspection/inferNullity/InferNullityAnnotationsAction.java @@ -49,6 +49,7 @@ import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.PsiUtil; import com.intellij.ui.TitledSeparator; import com.intellij.util.Function; +import com.intellij.util.SequentialModalProgressTask; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -196,7 +197,12 @@ public class InferNullityAnnotationsAction extends BaseAnalysisAction { new WriteCommandAction(project, INFER_NULLITY_ANNOTATIONS) { @Override protected void run(Result result) throws Throwable { - inferrer.apply(project); + if (!inferrer.nothingFoundMessage(project)) { + final SequentialModalProgressTask progressTask = new SequentialModalProgressTask(project, INFER_NULLITY_ANNOTATIONS, false); + progressTask.setMinIterationTime(200); + progressTask.setTask(new AnnotateTask(project, inferrer, progressTask)); + ProgressManager.getInstance().run(progressTask); + } } }.execute(); } diff --git a/java/java-impl/src/com/intellij/codeInspection/inferNullity/NullityInferrer.java b/java/java-impl/src/com/intellij/codeInspection/inferNullity/NullityInferrer.java index 9b67b5603a78..8abcb1262dbe 100644 --- a/java/java-impl/src/com/intellij/codeInspection/inferNullity/NullityInferrer.java +++ b/java/java-impl/src/com/intellij/codeInspection/inferNullity/NullityInferrer.java @@ -31,14 +31,15 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; +import java.util.ArrayList; import java.util.Collection; -import java.util.HashSet; +import java.util.List; public class NullityInferrer { private static final int MAX_PASSES = 10; private int numAnnotationsAdded = 0; - private final HashSet> myNotNullSet = new HashSet>(); - private final HashSet> myNullableSet = new HashSet>(); + private final List> myNotNullSet = new ArrayList>(); + private final List> myNullableSet = new ArrayList>(); private final boolean myAnnotateLocalVariables; private final SmartPointerManager myPointerManager; @@ -139,29 +140,60 @@ public class NullityInferrer { public void apply(final Project project) { final NullableNotNullManager manager = NullableNotNullManager.getInstance(project); for (SmartPsiElementPointer pointer : myNullableSet) { - final PsiModifierListOwner element = pointer.getElement(); - if (element != null) { - if (shouldIgnore(element)) continue; - new AddAnnotationFix(manager.getDefaultNullable(), element, manager.getDefaultNotNull()).invoke(project, null, element.getContainingFile()); - } + annotateNullable(project, manager, pointer); } for (SmartPsiElementPointer pointer : myNotNullSet) { - final PsiModifierListOwner element = pointer.getElement(); - if (element != null) { - if (shouldIgnore(element)) continue; - if (element instanceof PsiField && ((PsiField)element).hasInitializer() && element.hasModifierProperty(PsiModifier.FINAL)) continue; - new AddAnnotationFix(manager.getDefaultNotNull(), element, manager.getDefaultNullable()).invoke(project, null, - element.getContainingFile()); - } + annotateNotNull(project, manager, pointer); } + nothingFoundMessage(project); + } + + public boolean nothingFoundMessage(final Project project) { if (myNullableSet.isEmpty() && myNotNullSet.isEmpty()) { SwingUtilities.invokeLater(new Runnable() { public void run() { Messages.showInfoMessage(project, "No places found to infer @Nullable/@NotNull", "Infer Nullity Results"); } }); + return true; + } + return false; + } + + private void annotateNotNull(Project project, + NullableNotNullManager manager, + SmartPsiElementPointer pointer) { + final PsiModifierListOwner element = pointer.getElement(); + if (element != null) { + if (shouldIgnore(element)) return; + if (element instanceof PsiField && ((PsiField)element).hasInitializer() && element.hasModifierProperty(PsiModifier.FINAL)) return; + new AddAnnotationFix(manager.getDefaultNotNull(), element, manager.getDefaultNullable()).invoke(project, null, + element.getContainingFile()); + } + } + + private void annotateNullable(Project project, + NullableNotNullManager manager, + SmartPsiElementPointer pointer) { + final PsiModifierListOwner element = pointer.getElement(); + if (element != null) { + if (shouldIgnore(element)) return; + new AddAnnotationFix(manager.getDefaultNullable(), element, manager.getDefaultNotNull()).invoke(project, null, element.getContainingFile()); + } + } + + public int getCount() { + return myNotNullSet.size() + myNullableSet.size(); + } + + public void apply(int i, Project project, NullableNotNullManager manager) { + if (i < myNullableSet.size()) { + annotateNullable(project, manager, myNullableSet.get(i)); + } else { + i -= myNullableSet.size(); + annotateNotNull(project, manager, myNotNullSet.get(i)); } } diff --git a/java/java-impl/src/com/intellij/codeInspection/reference/RefJavaManagerImpl.java b/java/java-impl/src/com/intellij/codeInspection/reference/RefJavaManagerImpl.java index 9d7aaeb2ea1b..d98c89203767 100644 --- a/java/java-impl/src/com/intellij/codeInspection/reference/RefJavaManagerImpl.java +++ b/java/java-impl/src/com/intellij/codeInspection/reference/RefJavaManagerImpl.java @@ -300,9 +300,9 @@ public class RefJavaManagerImpl extends RefJavaManager { public void export(final RefEntity refEntity, final Element element) { if (refEntity instanceof RefElement) { - final PsiElement psiElement = ((RefElement)refEntity).getElement(); - if (psiElement != null) { - final PsiFile psiFile = psiElement.getContainingFile(); + final SmartPsiElementPointer pointer = ((RefElement)refEntity).getPointer(); + if (pointer != null) { + final PsiFile psiFile = pointer.getContainingFile(); if (psiFile instanceof PsiJavaFile) { appendPackageElement(element, ((PsiJavaFile)psiFile).getPackageName()); } diff --git a/java/java-impl/src/com/intellij/openapi/roots/impl/CompilerProjectExtensionImpl.java b/java/java-impl/src/com/intellij/openapi/roots/impl/CompilerProjectExtensionImpl.java index b491285c9cef..328faa3b77e1 100644 --- a/java/java-impl/src/com/intellij/openapi/roots/impl/CompilerProjectExtensionImpl.java +++ b/java/java-impl/src/com/intellij/openapi/roots/impl/CompilerProjectExtensionImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2012 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -98,12 +98,7 @@ public class CompilerProjectExtensionImpl extends CompilerProjectExtension { public void setCompilerOutputUrl(String compilerOutputUrl) { VirtualFilePointer pointer = VirtualFilePointerManager.getInstance().create(compilerOutputUrl, myProject, null); setCompilerOutputPointer(pointer); - final LocalFileSystem.WatchRequest watchRequest = - LocalFileSystem.getInstance().addRootToWatch(ProjectRootManagerImpl.extractLocalPath(compilerOutputUrl), true); - if (myCompilerOutputWatchRequest != null) { - LocalFileSystem.getInstance().removeWatchedRoot(myCompilerOutputWatchRequest); - } - myCompilerOutputWatchRequest = watchRequest; + myCompilerOutputWatchRequest = LocalFileSystem.getInstance().replaceWatchedRoot(myCompilerOutputWatchRequest, compilerOutputUrl, true); } @NotNull diff --git a/java/java-impl/src/com/intellij/psi/filters/getters/ClassLiteralGetter.java b/java/java-impl/src/com/intellij/psi/filters/getters/ClassLiteralGetter.java index d85d6ab526b2..4d44ef4541a9 100644 --- a/java/java-impl/src/com/intellij/psi/filters/getters/ClassLiteralGetter.java +++ b/java/java-impl/src/com/intellij/psi/filters/getters/ClassLiteralGetter.java @@ -25,6 +25,7 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; import com.intellij.psi.util.InheritanceUtil; import com.intellij.psi.util.PsiUtil; +import com.intellij.psi.util.TypeConversionUtil; import com.intellij.util.Consumer; import com.intellij.util.IncorrectOperationException; import org.jetbrains.annotations.NotNull; @@ -45,7 +46,7 @@ public class ClassLiteralGetter { PsiElement position = parameters.getPosition(); if (classParameter instanceof PsiWildcardType) { final PsiWildcardType wildcardType = (PsiWildcardType)classParameter; - classParameter = wildcardType.getBound(); + classParameter = wildcardType.isSuper() ? wildcardType.getSuperBound() : wildcardType.getExtendsBound(); addInheritors = wildcardType.isExtends() && classParameter instanceof PsiClassType; } else if (!matcher.getPrefix().isEmpty()) { addInheritors = true; diff --git a/java/java-psi-api/src/com/intellij/psi/PsiWildcardType.java b/java/java-psi-api/src/com/intellij/psi/PsiWildcardType.java index b089a229aa2f..4bc9f66c28c1 100644 --- a/java/java-psi-api/src/com/intellij/psi/PsiWildcardType.java +++ b/java/java-psi-api/src/com/intellij/psi/PsiWildcardType.java @@ -19,6 +19,7 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Key; import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.util.PsiUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -66,6 +67,11 @@ public class PsiWildcardType extends PsiType { public static PsiWildcardType changeBound(@NotNull PsiWildcardType type, @NotNull PsiType newBound) { LOG.assertTrue(type.getBound() != null); + if (type.myIsExtending) { + if (newBound.equalsToText(CommonClassNames.JAVA_LANG_OBJECT)) { + return createUnbounded(type.myManager); + } + } return new PsiWildcardType(type.myManager, type.myIsExtending, newBound); } diff --git a/java/java-psi-api/src/com/intellij/psi/util/TypeConversionUtil.java b/java/java-psi-api/src/com/intellij/psi/util/TypeConversionUtil.java index ab97e1b3b53d..22e0c9dc0a0b 100644 --- a/java/java-psi-api/src/com/intellij/psi/util/TypeConversionUtil.java +++ b/java/java-psi-api/src/com/intellij/psi/util/TypeConversionUtil.java @@ -659,7 +659,7 @@ public class TypeConversionUtil { return left.equals(right) || isAssignable(((PsiCapturedWildcardType)left).getLowerBound(), right, false); } if (right instanceof PsiCapturedWildcardType) { - return isAssignable(left, ((PsiCapturedWildcardType)right).getUpperBound(), false); + return isAssignable(left, ((PsiCapturedWildcardType)right).getUpperBound(), allowUncheckedConversion); } if (left instanceof PsiWildcardType) { diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting/CapturedWildcardAssignments.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting/CapturedWildcardAssignments.java index f105923eb1b1..b750cd27b9a7 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting/CapturedWildcardAssignments.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting/CapturedWildcardAssignments.java @@ -14,7 +14,7 @@ * limitations under the License. */ -import java.util.Iterator; +import java.util.*; public class Test { public static void bar() { @@ -24,4 +24,16 @@ public class Test { public static Class foo() { return null; } -} \ No newline at end of file +} + +class Example { + static List treeNodes = null; + + public static void main(String[] args) { + for (AbstractTreeNode treeNode : treeNodes) { + + } + } + +} +class AbstractTreeNode {} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting/IDEADEV57343.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting/IDEADEV57343.java index 63ecda1fd9e2..30456212a21b 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting/IDEADEV57343.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting/IDEADEV57343.java @@ -1,6 +1,6 @@ class D { void foo(D x){ - bar(x,x); + bar(x,x); } void bar(D x, D y){} } \ No newline at end of file diff --git a/java/java-tests/testData/find/findInClassHierarchy/src/A.java b/java/java-tests/testData/find/findInClassHierarchy/src/A.java new file mode 100644 index 000000000000..285f551b4789 --- /dev/null +++ b/java/java-tests/testData/find/findInClassHierarchy/src/A.java @@ -0,0 +1,18 @@ +public class A { + void foo(Object o) { + if (o instanceof String){} + } +} + +class AImpl extends A { + void foo(Object o) { + if (o instanceof String){} + } +} + +class B { + void bar(Object o) { + if (o instanceof String){} + } +} + diff --git a/java/java-tests/testData/find/findInPath/src/s.java b/java/java-tests/testData/find/findInPath/src/s.java new file mode 100644 index 000000000000..02306018d545 --- /dev/null +++ b/java/java-tests/testData/find/findInPath/src/s.java @@ -0,0 +1,3 @@ +class s { + String s = "xx.yy"; +} \ No newline at end of file diff --git a/java/java-tests/testData/find/findInPath/src/x/dd.properties b/java/java-tests/testData/find/findInPath/src/x/dd.properties new file mode 100644 index 000000000000..f2e9b8253292 --- /dev/null +++ b/java/java-tests/testData/find/findInPath/src/x/dd.properties @@ -0,0 +1,3 @@ +xx.yy=zzzz +aaa.bbb=zzz +cccc=c \ No newline at end of file diff --git a/java/java-tests/testData/find/findManager/src/test/FindTest.java b/java/java-tests/testData/find/findManager/src/test/FindTest.java new file mode 100644 index 000000000000..6cd59ff2b476 --- /dev/null +++ b/java/java-tests/testData/find/findManager/src/test/FindTest.java @@ -0,0 +1,11 @@ +package test; +public class FindTest { + public static void main(String[] args) { + System.out.println(" done"); +//done + System.out.println("done"); + System.out.println(" done"); + System.out.println(" done2"); +//done! + } +} \ No newline at end of file diff --git a/java/java-tests/testData/find/findManager/src1/tests/FindTests.java b/java/java-tests/testData/find/findManager/src1/tests/FindTests.java new file mode 100644 index 000000000000..657becd99530 --- /dev/null +++ b/java/java-tests/testData/find/findManager/src1/tests/FindTests.java @@ -0,0 +1,11 @@ +package tests; +public class FindTests { + public static void main(String[] args) { + System.out.println(" done"); +//done + System.out.println("done"); + System.out.println(" done"); + System.out.println(" done2"); +//done! + } +} \ No newline at end of file diff --git a/java/java-tests/testData/find/libUnderProject/lib/B.java b/java/java-tests/testData/find/libUnderProject/lib/B.java new file mode 100644 index 000000000000..634b3185283e --- /dev/null +++ b/java/java-tests/testData/find/libUnderProject/lib/B.java @@ -0,0 +1 @@ +// TargetWord \ No newline at end of file diff --git a/java/java-tests/testData/find/libUnderProject/src/A.java b/java/java-tests/testData/find/libUnderProject/src/A.java new file mode 100644 index 000000000000..634b3185283e --- /dev/null +++ b/java/java-tests/testData/find/libUnderProject/src/A.java @@ -0,0 +1 @@ +// TargetWord \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/find/FindManagerTest.java b/java/java-tests/testSrc/com/intellij/find/FindManagerTest.java new file mode 100644 index 000000000000..660ba9dcfe0c --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/find/FindManagerTest.java @@ -0,0 +1,428 @@ +package com.intellij.find; + +import com.intellij.JavaTestUtil; +import com.intellij.codeInsight.daemon.DaemonAnalyzerTestCase; +import com.intellij.find.impl.FindInProjectUtil; +import com.intellij.find.replaceInProject.ReplaceInProjectManager; +import com.intellij.lang.properties.IProperty; +import com.intellij.lang.properties.psi.PropertiesFile; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ex.PathManagerEx; +import com.intellij.openapi.fileTypes.FileTypes; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.projectRoots.impl.JavaSdkImpl; +import com.intellij.openapi.roots.ContentEntry; +import com.intellij.openapi.roots.ModifiableRootModel; +import com.intellij.openapi.roots.ModuleRootManager; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.*; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.LocalSearchScope; +import com.intellij.testFramework.PlatformTestUtil; +import com.intellij.testFramework.PsiTestUtil; +import com.intellij.testFramework.fixtures.TempDirTestFixture; +import com.intellij.testFramework.fixtures.impl.LightTempDirTestFixtureImpl; +import com.intellij.usageView.UsageInfo; +import com.intellij.usages.Usage; +import com.intellij.util.ArrayUtil; +import com.intellij.util.ThrowableRunnable; +import com.intellij.util.WaitFor; + +import java.io.File; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/* + * @author: MYakovlev + * Date: Oct 17, 2002 + * Time: 7:53:31 PM + */ +@SuppressWarnings({"HardCodedStringLiteral"}) +public class FindManagerTest extends DaemonAnalyzerTestCase { + protected VirtualFile[] mySourceDirs; + + public void testFindString() throws Exception{ + FindManager findManager = FindManager.getInstance(myProject); + + FindModel findModel = new FindModel(); + findModel.setStringToFind("done"); + findModel.setWholeWordsOnly(false); + findModel.setFromCursor(false); + findModel.setGlobal(true); + findModel.setMultipleFiles(false); + findModel.setProjectScope(true); + + String text = "public static class MyClass{\n/*done*/\npublic static void main(){}}"; + FindResult findResult = findManager.findString(text, 0, findModel); + assertTrue(findResult.isStringFound()); + + findModel = new FindModel(); + findModel.setStringToFind("done"); + findModel.setWholeWordsOnly(false); + findModel.setFromCursor(true); + findModel.setGlobal(true); + findModel.setMultipleFiles(false); + findModel.setProjectScope(true); + + findResult = findManager.findString(text, 40, findModel); + assertFalse(findResult.isStringFound()); + + findModel = new FindModel(); + findModel.setStringToFind("done"); + findModel.setWholeWordsOnly(false); + findModel.setFromCursor(true); + findModel.setGlobal(true); + findModel.setMultipleFiles(false); + findModel.setProjectScope(true); + findModel.setForward(false); + + findResult = findManager.findString(text, 40, findModel); + assertTrue(findResult.isStringFound()); + + findModel = new FindModel(); + findModel.setStringToFind("done"); + findModel.setWholeWordsOnly(true); + findModel.setFromCursor(false); + findModel.setGlobal(true); + findModel.setMultipleFiles(false); + findModel.setProjectScope(true); + + findResult = findManager.findString(text, 0, findModel); + assertTrue(findResult.isStringFound()); + + findModel = new FindModel(); + findModel.setStringToFind("don"); + findModel.setWholeWordsOnly(true); + findModel.setFromCursor(false); + findModel.setGlobal(true); + findModel.setMultipleFiles(false); + findModel.setProjectScope(true); + + final FindResult[] findResultArr = new FindResult[1]; + findInNewThread(findModel, findManager, text, 0, findResultArr); + new WaitFor(30 *1000){ + @Override + protected boolean condition() { + return findResultArr[0] != null; + } + }.assertCompleted(); + + assertFalse(findResultArr[0].isStringFound()); + } + + private static Thread findInNewThread(final FindModel model, + final FindManager findManager, + final CharSequence text, + final int offset, + final FindResult[] op_result){ + op_result[0] = null; + Thread findThread = new Thread(){ + @Override + public void run(){ + op_result[0] = findManager.findString(text, offset, model); + } + }; + findThread.start(); + return findThread; + } + + public void testFindUsages() throws Exception{ + initProject("findManager", "src", "src1"); + final String projectDir = (PathManagerEx.getTestDataPath() + "/find/findManager").replace('/', File.separatorChar); + + FindModel findModel = new FindModel(); + findModel.setStringToFind("done"); + findModel.setWholeWordsOnly(false); + findModel.setFromCursor(false); + findModel.setGlobal(true); + findModel.setMultipleFiles(true); + findModel.setProjectScope(true); + findModel.setDirectoryName(projectDir + File.separatorChar + "src1"); + findModel.setWithSubdirectories(true); + checkFindUsages(12, findModel); + + //findModel = new FindModel(); + findModel.setFromCursor(false); + findModel.setGlobal(true); + findModel.setMultipleFiles(true); + findModel.setProjectScope(false); + findModel.setDirectoryName(projectDir + File.separatorChar + "src1"); + findModel.setWithSubdirectories(true); + checkFindUsages(6, findModel); + + findModel.setWholeWordsOnly(true); + checkFindUsages(5, findModel); +// findModel.setForward(false); +// findModel.setCaseSensitive(); + + + } + + private void checkFindUsages(int expectedResults, FindModel findModel) throws Exception{ + Collection usages = findUsages(findModel); + assertEquals(expectedResults, usages.size()); + } + + private List findUsages(final FindModel findModel) { + PsiDirectory psiDirectory = FindInProjectUtil.getPsiDirectory(findModel, myProject); + return FindInProjectUtil.findUsages(findModel, psiDirectory, myProject); + } + + public void testFindWholeWordsInProperties() throws Exception { + initProject("findInPath", "src"); + + searchProperty("xx.yy"); + searchProperty(".yy"); + searchProperty("xx."); + } + + private void searchProperty(String query) { + FindModel findModel = new FindModel(); + findModel.setStringToFind(query); + findModel.setWholeWordsOnly(true); + findModel.setFromCursor(false); + findModel.setGlobal(true); + findModel.setMultipleFiles(true); + findModel.setProjectScope(true); + findModel.setDirectoryName(mySourceDirs[0].getPath()); + findModel.setWithSubdirectories(true); + + List usages = findUsages(findModel); + assertEquals(2, usages.size()); + if (!(usages.get(0).getElement().getContainingFile() instanceof PsiJavaFile)) { + Collections.swap(usages, 0, 1); + } + PsiElement element = usages.get(0).getElement(); + //assertTrue(element instanceof PsiJavaFile); + PsiElement refElement = element.findElementAt(usages.get(0).getRangeInElement().getStartOffset()).getParent(); + assertTrue(refElement instanceof PsiLiteralExpression); + assertEquals("xx.yy", ((PsiLiteralExpression)refElement).getValue()); + + VirtualFile file = mySourceDirs[0].findFileByRelativePath("x/dd.properties"); + PropertiesFile propertiesFile = (PropertiesFile)PsiManager.getInstance(myProject).findFile(file); + element = usages.get(1).getElement(); + //assertTrue(element instanceof PropertiesFile); + refElement = element.findElementAt(usages.get(1).getRangeInElement().getStartOffset()).getParent(); + assertTrue(refElement instanceof IProperty); + assertSame(propertiesFile.findPropertyByKey("xx.yy"), refElement); + } + + public void testFindInClassHierarchy() throws Exception { + initProject("findInClassHierarchy", "src"); + + FindModel findModel = new FindModel(); + findModel.setStringToFind("instanceof"); + findModel.setWholeWordsOnly(true); + findModel.setFromCursor(false); + findModel.setGlobal(true); + findModel.setMultipleFiles(true); + final JavaPsiFacade facade = JavaPsiFacade.getInstance(getProject()); + final PsiClass baseClass = facade.findClass("A", GlobalSearchScope.allScope(getProject())); + final PsiClass implClass = facade.findClass("AImpl", GlobalSearchScope.allScope(getProject())); + findModel.setCustomScope(new LocalSearchScope(new PsiElement[]{baseClass, implClass})); + + List usages = findUsages(findModel); + assertEquals(2, usages.size()); + } + + public void testReplaceRegexp() throws Throwable { + FindManager findManager = FindManager.getInstance(myProject); + + FindModel findModel = new FindModel(); + findModel.setStringToFind("bug(?=here)"); + findModel.setStringToReplace("x$0y"); + findModel.setWholeWordsOnly(false); + findModel.setFromCursor(false); + findModel.setGlobal(true); + findModel.setMultipleFiles(false); + findModel.setProjectScope(true); + findModel.setRegularExpressions(true); + findModel.setPromptOnReplace(false); + + findManager.setFindNextModel(null); + findManager.getFindInFileModel().copyFrom(findModel); + + String text = "bughere\n" + "bughere"; + configureByText(FileTypes.PLAIN_TEXT, text); + boolean succ = FindUtil.replace(getProject(), getEditor(), 0, findModel); + assertTrue(succ); + + assertEquals("xbugyhere\n" + "xbugyhere", getEditor().getDocument().getText()); + } + public void testReplaceRegexp1() throws Throwable { + FindManager findManager = FindManager.getInstance(myProject); + + FindModel findModel = new FindModel(); + findModel.setStringToFind("bug(?=here)"); + findModel.setStringToReplace("$0"); + findModel.setWholeWordsOnly(false); + findModel.setFromCursor(false); + findModel.setGlobal(true); + findModel.setMultipleFiles(false); + findModel.setProjectScope(true); + findModel.setRegularExpressions(true); + findModel.setPromptOnReplace(false); + + findManager.setFindNextModel(null); + findManager.getFindInFileModel().copyFrom(findModel); + + String text = "bughere\n" + "bughere"; + configureByText(FileTypes.PLAIN_TEXT, text); + boolean succ = FindUtil.replace(getProject(), getEditor(), 0, findModel); + assertTrue(succ); + + assertEquals(text, getEditor().getDocument().getText()); + } + + public void testReplaceRegexpWithNewLine() throws Throwable { + FindManager findManager = FindManager.getInstance(myProject); + + FindModel findModel = new FindModel(); + findModel.setStringToFind("xxx"); + findModel.setStringToReplace("xxx\\n"); + findModel.setWholeWordsOnly(false); + findModel.setFromCursor(false); + findModel.setGlobal(true); + findModel.setMultipleFiles(false); + findModel.setProjectScope(true); + findModel.setRegularExpressions(true); + findModel.setPromptOnReplace(false); + + findManager.setFindNextModel(null); + findManager.getFindInFileModel().copyFrom(findModel); + + String text = "xxx"; + configureByText(FileTypes.PLAIN_TEXT, text); + boolean succ = FindUtil.replace(getProject(), getEditor(), 0, findModel); + assertTrue(succ); + + assertEquals(text+"\n", getEditor().getDocument().getText()); + } + + private void initProject(String folderName, final String... sourceDirs) throws Exception{ + final String testDir = JavaTestUtil.getJavaTestDataPath() + "/find/" + folderName; + ApplicationManager.getApplication().runWriteAction(new Runnable(){ + @Override + public void run(){ + try{ + mySourceDirs = new VirtualFile[sourceDirs.length]; + for (int i = 0; i < sourceDirs.length; i++){ + String sourceDir = sourceDirs[i]; + mySourceDirs[i] = LocalFileSystem.getInstance().refreshAndFindFileByPath(new File(testDir + File.separatorChar + sourceDir).getCanonicalPath().replace(File.separatorChar, '/')); + } + VirtualFile projectDir = LocalFileSystem.getInstance().refreshAndFindFileByPath(new File(testDir).getCanonicalPath().replace(File.separatorChar, '/')); + + final ModuleRootManager rootManager = ModuleRootManager.getInstance(myModule); + final ModifiableRootModel rootModel = rootManager.getModifiableModel(); + rootModel.clear(); + // configure source and output path + final ContentEntry contentEntry = rootModel.addContentEntry(projectDir); + for (VirtualFile sourceDir : mySourceDirs) { + contentEntry.addSourceFolder(sourceDir, false); + } + + Sdk jdk = JavaSdkImpl.getMockJdk17(); + rootModel.setSdk(jdk); + + rootModel.commit(); + } + catch (Exception e){ + throw new RuntimeException(e); + } + } + }); + } + + public void testReplaceAll() throws Throwable { + FindManager findManager = FindManager.getInstance(myProject); + + FindModel findModel = new FindModel(); + String toFind = "xxx"; + String toReplace = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; + findModel.setStringToFind(toFind); + findModel.setStringToReplace(toReplace); + findModel.setWholeWordsOnly(true); + findModel.setFromCursor(false); + findModel.setGlobal(true); + findModel.setMultipleFiles(false); + findModel.setProjectScope(true); + findModel.setRegularExpressions(false); + findModel.setPromptOnReplace(false); + + findManager.setFindNextModel(null); + findManager.getFindInFileModel().copyFrom(findModel); + + String text = StringUtil.repeat(toFind + "\n",6); + configureByText(FileTypes.PLAIN_TEXT, text); + + List usages = FindUtil.findAll(getProject(), myEditor, findModel); + for (Usage usage : usages) { + ReplaceInProjectManager.getInstance(getProject()).doReplace(usage, findModel, Collections.emptySet()); + } + String newText = StringUtil.repeat(toReplace + "\n",6); + assertEquals(newText, getEditor().getDocument().getText()); + } + + public void testFindInFileUnderLibraryUnderProject() throws Exception { + initProject("libUnderProject", "src"); + PsiTestUtil.addLibrary(myModule, "lib", JavaTestUtil.getJavaTestDataPath() + "/find/libUnderProject/lib", new String[]{""}, ArrayUtil.EMPTY_STRING_ARRAY); + + FindModel findModel = new FindModel(); + findModel.setStringToFind("TargetWord"); + findModel.setFromCursor(false); + findModel.setGlobal(true); + findModel.setMultipleFiles(true); + + findModel.setWholeWordsOnly(false); + assertSize(2, findUsages(findModel)); + + /* todo + findModel.setWholeWordsOnly(true); + assertSize(2, findUsages(findModel)); + */ + } + + public void testLocalScopeSearchPerformance() throws Exception { + final int count = 3000; + TempDirTestFixture fixture = new LightTempDirTestFixtureImpl(); + fixture.setUp(); + + try { + String sampleText = StringUtil.repeat("zoo TargetWord foo bar goo\n", count); + for (int i = 0; i < count; i++) { + fixture.createFile("a" + i + ".txt", sampleText); + } + PsiTestUtil.addSourceContentToRoots(myModule, fixture.getFile("")); + + VirtualFile file = fixture.createFile("target.txt", sampleText); + PsiFile psiFile = PsiManager.getInstance(myProject).findFile(file); + final FindModel findModel = new FindModel(); + findModel.setStringToFind("TargetWord"); + findModel.setWholeWordsOnly(true); + findModel.setFromCursor(false); + findModel.setGlobal(true); + findModel.setMultipleFiles(true); + + ThrowableRunnable test = new ThrowableRunnable() { + @Override + public void run() throws Throwable { + assertSize(count, findUsages(findModel)); + } + }; + + findModel.setCustomScope(GlobalSearchScope.fileScope(psiFile)); + PlatformTestUtil.startPerformanceTest("slow", 500, test).attempts(1).cpuBound().usesAllCPUCores().assertTiming(); + + findModel.setCustomScope(new LocalSearchScope(psiFile)); + PlatformTestUtil.startPerformanceTest("slow", 500, test).attempts(1).cpuBound().usesAllCPUCores().assertTiming(); + } + finally { + fixture.tearDown(); + } + } + + +} diff --git a/java/java-tests/testSrc/com/intellij/psi/resolve/ResolveClassTest.java b/java/java-tests/testSrc/com/intellij/psi/resolve/ResolveClassTest.java index 8eab910851f0..93614eae30b7 100644 --- a/java/java-tests/testSrc/com/intellij/psi/resolve/ResolveClassTest.java +++ b/java/java-tests/testSrc/com/intellij/psi/resolve/ResolveClassTest.java @@ -170,7 +170,7 @@ public class ResolveClassTest extends ResolveTestCase { PsiReference ref = configure(); long start = System.currentTimeMillis(); assertNull(ref.resolve()); - PlatformTestUtil.assertTiming("exponent?", 20000, System.currentTimeMillis() - start); + PlatformTestUtil.assertTiming("exponent?", 200, System.currentTimeMillis() - start); } public void testStaticImportNetwork() throws Exception { diff --git a/jps/jps-builders/src/org/jetbrains/jps/api/AsyncTaskExecutor.java b/jps/jps-builders/src/org/jetbrains/jps/api/AsyncTaskExecutor.java deleted file mode 100644 index 745834c7de69..000000000000 --- a/jps/jps-builders/src/org/jetbrains/jps/api/AsyncTaskExecutor.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.jetbrains.jps.api; - -/** -* @author Eugene Zhuravlev -* Date: 2/28/12 -*/ -public interface AsyncTaskExecutor { - AsyncTaskExecutor DEFAULT = new AsyncTaskExecutor() { - @Override - public void submit(Runnable runnable) { - new Thread(runnable).start(); - } - }; - - void submit(Runnable runnable); -} diff --git a/jps/jps-builders/src/org/jetbrains/jps/api/SequentialTaskExecutor.java b/jps/jps-builders/src/org/jetbrains/jps/api/SequentialTaskExecutor.java index de47b287cca2..4bc851c017d9 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/api/SequentialTaskExecutor.java +++ b/jps/jps-builders/src/org/jetbrains/jps/api/SequentialTaskExecutor.java @@ -1,6 +1,7 @@ package org.jetbrains.jps.api; import java.util.Queue; +import java.util.concurrent.Executor; import java.util.concurrent.FutureTask; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RunnableFuture; @@ -11,7 +12,7 @@ import java.util.concurrent.atomic.AtomicBoolean; * Date: 9/24/11 */ public class SequentialTaskExecutor { - private final AsyncTaskExecutor myExecutor; + private final Executor myExecutor; private final Queue myTaskQueue = new LinkedBlockingQueue(); private final AtomicBoolean myInProgress = new AtomicBoolean(false); private final Runnable USER_TASK_RUNNER = new Runnable() { @@ -31,7 +32,7 @@ public class SequentialTaskExecutor { } }; - public SequentialTaskExecutor(AsyncTaskExecutor executor) { + public SequentialTaskExecutor(Executor executor) { myExecutor = executor; } @@ -48,7 +49,7 @@ public class SequentialTaskExecutor { private void processQueue() { if (!myInProgress.getAndSet(true)) { - myExecutor.submit(USER_TASK_RUNNER); + myExecutor.execute(USER_TASK_RUNNER); } } diff --git a/jps/jps-builders/src/org/jetbrains/jps/api/SharedThreadPool.java b/jps/jps-builders/src/org/jetbrains/jps/api/SharedThreadPool.java index 508abe015b3f..da26e58c6ff0 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/api/SharedThreadPool.java +++ b/jps/jps-builders/src/org/jetbrains/jps/api/SharedThreadPool.java @@ -9,10 +9,4 @@ import java.util.concurrent.Executors; */ public class SharedThreadPool { public static final ExecutorService INSTANCE = Executors.newCachedThreadPool(); - public static final AsyncTaskExecutor ASYNC_EXEC = new AsyncTaskExecutor() { - @Override - public void submit(Runnable runnable) { - INSTANCE.submit(runnable); - } - }; } diff --git a/jps/jps-builders/src/org/jetbrains/jps/client/CompileServerClient.java b/jps/jps-builders/src/org/jetbrains/jps/client/CompileServerClient.java index 72cfb5850034..2f263acb40b0 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/client/CompileServerClient.java +++ b/jps/jps-builders/src/org/jetbrains/jps/client/CompileServerClient.java @@ -6,7 +6,11 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jps.api.*; -import java.util.*; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.Executor; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -20,7 +24,7 @@ public class CompileServerClient extends SimpleProtobufClient myPingFuture; private final long myServerPingInterval; - public CompileServerClient(long serverPingInterval, final AsyncTaskExecutor asyncExec) { + public CompileServerClient(long serverPingInterval, final Executor asyncExec) { super(JpsRemoteProto.Message.getDefaultInstance(), asyncExec, new UUIDGetter() { @NotNull public UUID getSessionUUID(@NotNull MessageEvent e) { diff --git a/jps/jps-builders/src/org/jetbrains/jps/client/ProtobufClientMessageHandler.java b/jps/jps-builders/src/org/jetbrains/jps/client/ProtobufClientMessageHandler.java index 9c6477f29512..ecb25bd5ec45 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/client/ProtobufClientMessageHandler.java +++ b/jps/jps-builders/src/org/jetbrains/jps/client/ProtobufClientMessageHandler.java @@ -6,12 +6,12 @@ import org.jboss.netty.channel.ChannelStateEvent; import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.channel.SimpleChannelHandler; import org.jetbrains.annotations.NotNull; -import org.jetbrains.jps.api.AsyncTaskExecutor; import org.jetbrains.jps.api.RequestFuture; import java.util.ArrayList; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; /** * @author Eugene Zhuravlev @@ -22,9 +22,9 @@ final class ProtobufClientMessageHandler exte @NotNull private final UUIDGetter myUuidGetter; private final SimpleProtobufClient myClient; - private final AsyncTaskExecutor myAsyncExec; + private final Executor myAsyncExec; - public ProtobufClientMessageHandler(@NotNull UUIDGetter uuidGetter, SimpleProtobufClient client, AsyncTaskExecutor asyncExec) { + public ProtobufClientMessageHandler(@NotNull UUIDGetter uuidGetter, SimpleProtobufClient client, Executor asyncExec) { myUuidGetter = uuidGetter; myClient = client; myAsyncExec = asyncExec; @@ -92,7 +92,7 @@ final class ProtobufClientMessageHandler exte } finally { // make sure the client is in disconnected state - myAsyncExec.submit(new Runnable() { + myAsyncExec.execute(new Runnable() { @Override public void run() { myClient.disconnect(); diff --git a/jps/jps-builders/src/org/jetbrains/jps/client/SimpleProtobufClient.java b/jps/jps-builders/src/org/jetbrains/jps/client/SimpleProtobufClient.java index 19544249f0c5..8260ff486704 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/client/SimpleProtobufClient.java +++ b/jps/jps-builders/src/org/jetbrains/jps/client/SimpleProtobufClient.java @@ -10,13 +10,11 @@ import org.jboss.netty.handler.codec.protobuf.ProtobufEncoder; import org.jboss.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder; import org.jboss.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender; import org.jetbrains.annotations.Nullable; -import org.jetbrains.jps.api.AsyncTaskExecutor; import org.jetbrains.jps.api.RequestFuture; import java.net.InetSocketAddress; import java.util.UUID; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; +import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicReference; /** @@ -25,7 +23,6 @@ import java.util.concurrent.atomic.AtomicReference; */ public class SimpleProtobufClient { private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.client.SimpleProtobufClient"); - private final ExecutorService ourExecutor = Executors.newCachedThreadPool(); private static enum State { DISCONNECTED, CONNECTING, CONNECTED, DISCONNECTING @@ -37,9 +34,9 @@ public class SimpleProtobufClient { protected volatile ChannelFuture myConnectFuture; private final ProtobufClientMessageHandler myMessageHandler; - public SimpleProtobufClient(final MessageLite msgDefaultInstance, final AsyncTaskExecutor asyncExec, final UUIDGetter uuidGetter) { + public SimpleProtobufClient(final MessageLite msgDefaultInstance, final Executor asyncExec, final UUIDGetter uuidGetter) { myMessageHandler = new ProtobufClientMessageHandler(uuidGetter, this, asyncExec); - myChannelFactory = new NioClientSocketChannelFactory(ourExecutor, ourExecutor, 1); + myChannelFactory = new NioClientSocketChannelFactory(asyncExec, asyncExec, 1); myPipelineFactory = new ChannelPipelineFactory() { public ChannelPipeline getPipeline() throws Exception { return Channels.pipeline( @@ -117,13 +114,8 @@ public class SimpleProtobufClient { catch (Throwable e) { LOG.error(e); } - try { - final ChannelFuture closeFuture = future.getChannel().close(); - closeFuture.awaitUninterruptibly(); - } - finally { - myChannelFactory.releaseExternalResources(); - } + final ChannelFuture closeFuture = future.getChannel().close(); + closeFuture.awaitUninterruptibly(); } } finally { diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/IncProjectBuilder.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/IncProjectBuilder.java index a84911754c50..4eb3dfa3f5ac 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/incremental/IncProjectBuilder.java +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/IncProjectBuilder.java @@ -7,13 +7,13 @@ import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.io.MappingFailedException; import com.intellij.util.io.PersistentEnumerator; -import org.jetbrains.annotations.Nullable; import org.jetbrains.jps.*; import org.jetbrains.jps.api.CanceledStatus; import org.jetbrains.jps.api.GlobalOptions; import org.jetbrains.jps.api.RequestFuture; import org.jetbrains.jps.api.SharedThreadPool; import org.jetbrains.jps.incremental.java.ExternalJavacDescriptor; +import org.jetbrains.jps.incremental.java.JavaBuilder; import org.jetbrains.jps.incremental.java.JavaBuilderLogger; import org.jetbrains.jps.incremental.messages.BuildMessage; import org.jetbrains.jps.incremental.messages.CompilerMessage; @@ -164,7 +164,7 @@ public class IncProjectBuilder { return context; } - private static void flushContext(@Nullable CompileContext context) { + private static void flushContext(CompileContext context) { if (context != null) { context.getTimestampStorage().force(); context.getDataManager().flush(false); @@ -181,16 +181,14 @@ public class IncProjectBuilder { } ExternalJavacDescriptor.KEY.set(context, null); } - if (context == null || context.getBuilderParameter(GlobalOptions.USE_EXTERNAL_JAVAC_OPTION) == null) { - cleanupJavacNameTable(); - } + cleanupJavacNameTable(); } private static boolean ourClenupFailed = false; private static void cleanupJavacNameTable() { try { - if (!ourClenupFailed) { + if (JavaBuilder.USE_EMBEDDED_JAVAC && !ourClenupFailed) { final Field freelistField = Class.forName("com.sun.tools.javac.util.Name$Table").getDeclaredField("freelist"); freelistField.setAccessible(true); freelistField.set(null, com.sun.tools.javac.util.List.nil()); diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/java/JavaBuilder.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/java/JavaBuilder.java index 14e158ff66a4..64d96061c30c 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/incremental/java/JavaBuilder.java +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/java/JavaBuilder.java @@ -56,6 +56,7 @@ public class JavaBuilder extends ModuleLevelBuilder { private static final String FORMS_BUILDER_NAME = "forms"; private static final String JAVA_EXTENSION = ".java"; private static final String FORM_EXTENSION = ".form"; + public static final boolean USE_EMBEDDED_JAVAC = System.getProperty(GlobalOptions.USE_EXTERNAL_JAVAC_OPTION) == null; public static final FileFilter JAVA_SOURCES_FILTER = new FileFilter() { public boolean accept(File file) { @@ -385,7 +386,7 @@ public class JavaBuilder extends ModuleLevelBuilder { final ClassProcessingConsumer classesConsumer = new ClassProcessingConsumer(context, outputSink); try { final boolean rc; - if (context.getBuilderParameter(GlobalOptions.USE_EXTERNAL_JAVAC_OPTION) == null) { + if (USE_EMBEDDED_JAVAC) { rc = JavacMain.compile( options, files, classpath, platformCp, sourcePath, outs, diagnosticSink, classesConsumer, context.getCancelStatus() ); diff --git a/jps/jps-builders/src/org/jetbrains/jps/javac/JavacServerClient.java b/jps/jps-builders/src/org/jetbrains/jps/javac/JavacServerClient.java index 136f07ce4798..c32e27d5fe5e 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/javac/JavacServerClient.java +++ b/jps/jps-builders/src/org/jetbrains/jps/javac/JavacServerClient.java @@ -17,7 +17,7 @@ import java.util.*; public class JavacServerClient extends SimpleProtobufClient{ public JavacServerClient() { - super(JavacRemoteProto.Message.getDefaultInstance(), SharedThreadPool.ASYNC_EXEC, new UUIDGetter() { + super(JavacRemoteProto.Message.getDefaultInstance(), SharedThreadPool.INSTANCE, new UUIDGetter() { @NotNull public UUID getSessionUUID(@NotNull MessageEvent e) { final JavacRemoteProto.Message message = (JavacRemoteProto.Message)e.getMessage(); diff --git a/jps/jps-builders/src/org/jetbrains/jps/server/Server.java b/jps/jps-builders/src/org/jetbrains/jps/server/Server.java index baacea57e79d..9987b88e0550 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/server/Server.java +++ b/jps/jps-builders/src/org/jetbrains/jps/server/Server.java @@ -1,6 +1,7 @@ package org.jetbrains.jps.server; //import com.intellij.openapi.diagnostic.Logger; + import com.intellij.openapi.diagnostic.Logger; import com.intellij.util.ConcurrencyUtil; import org.apache.log4j.Level; @@ -17,17 +18,13 @@ import org.jboss.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder; import org.jboss.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.Nullable; -import org.jetbrains.jps.api.AsyncTaskExecutor; import org.jetbrains.jps.api.GlobalOptions; import org.jetbrains.jps.api.JpsRemoteProto; import org.jetbrains.jps.incremental.Utils; import java.io.File; import java.net.InetSocketAddress; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; /** * @author Eugene Zhuravlev @@ -70,21 +67,21 @@ public class Server { private final ScheduledExecutorService myScheduler; private final ServerMessageHandler myMessageHandler; - public Server(File systemDir, boolean cachesInMemory) { + public Server(File systemDir) { Utils.setSystemRoot(systemDir); final ExecutorService threadPool = Executors.newCachedThreadPool(); myScheduler = ConcurrencyUtil.newSingleScheduledThreadExecutor("Client activity checker", Thread.MIN_PRIORITY); myBuildsExecutor = Executors.newFixedThreadPool(MAX_SIMULTANEOUS_BUILD_SESSIONS); myChannelFactory = new NioServerSocketChannelFactory(threadPool, threadPool, 1); final ChannelRegistrar channelRegistrar = new ChannelRegistrar(); - myMessageHandler = new ServerMessageHandler(this, new AsyncTaskExecutor() { + myMessageHandler = new ServerMessageHandler(this, new Executor() { @Override - public void submit(final Runnable runnable) { + public void execute(final Runnable command) { myBuildsExecutor.submit(new Runnable() { @Override public void run() { try { - runnable.run(); + command.run(); } finally { Thread.interrupted(); // clear interrupted status before returning to pull @@ -105,17 +102,6 @@ public class Server { ); } }; - ServerState.getInstance().setKeepTempCachesInMemory(cachesInMemory); - Runtime.getRuntime().addShutdownHook(new Thread("Shutdown hook thread") { - public void run() { - try { - myMessageHandler.cancelAllBuildsAndClearState(); - } - finally { - Server.this.stop(); - } - } - }); } public void start(int listenPort) { @@ -192,10 +178,6 @@ public class Server { myLastPingTime = System.currentTimeMillis(); } - public boolean isStopped() { - return myScheduler.isShutdown(); - } - public static void main(String[] args) { try { int port = DEFAULT_SERVER_PORT; @@ -212,10 +194,21 @@ public class Server { systemDir = new File(args[1]); } - final Server server = new Server(systemDir, System.getProperty(GlobalOptions.USE_MEMORY_TEMP_CACHE_OPTION) != null); + final Server server = new Server(systemDir); + Runtime.getRuntime().addShutdownHook(new Thread("Shutdown hook thread") { + public void run() { + try { + server.myMessageHandler.cancelAllBuildsAndClearState(); + } + finally { + server.stop(); + } + } + }); initLoggers(); server.start(port); + ServerState.getInstance().setKeepTempCachesInMemory(System.getProperty(GlobalOptions.USE_MEMORY_TEMP_CACHE_OPTION) != null); System.out.println("Server classpath: " + System.getProperty("java.class.path")); System.err.println(SERVER_SUCCESS_START_MESSAGE + port); diff --git a/jps/jps-builders/src/org/jetbrains/jps/server/ServerMessageHandler.java b/jps/jps-builders/src/org/jetbrains/jps/server/ServerMessageHandler.java index 49fbfc7dcb26..c2f9fa3802cb 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/server/ServerMessageHandler.java +++ b/jps/jps-builders/src/org/jetbrains/jps/server/ServerMessageHandler.java @@ -15,6 +15,7 @@ import java.io.File; import java.io.PrintStream; import java.util.*; import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; import java.util.concurrent.RunnableFuture; /** @@ -27,9 +28,9 @@ class ServerMessageHandler extends SimpleChannelHandler { private final Map myTaskExecutors = new HashMap(); private final List> myBuildsInProgress = Collections.synchronizedList(new LinkedList>()); private final Server myServer; - private final AsyncTaskExecutor myAsyncExecutor; + private final Executor myAsyncExecutor; - public ServerMessageHandler(Server server, final AsyncTaskExecutor asyncExecutor) { + public ServerMessageHandler(Server server, final Executor asyncExecutor) { myServer = server; myAsyncExecutor = asyncExecutor; } @@ -80,13 +81,14 @@ class ServerMessageHandler extends SimpleChannelHandler { new GlobalLibrary(library.getName(), library.getPathList()) ); } - final String globalEncoding = setupCommand.isInitialized()? setupCommand.getGlobalEncoding() : null; - facade.setGlobals(libs, pathVars, globalEncoding, setupCommand.getIgnoredFilesPatterns()); + final String globalEncoding = setupCommand.hasGlobalEncoding()? setupCommand.getGlobalEncoding() : null; + final String ignoredPatterns = setupCommand.hasIgnoredFilesPatterns()? setupCommand.getIgnoredFilesPatterns() : null; + facade.setGlobals(libs, pathVars, globalEncoding, ignoredPatterns); reply = ProtoUtil.toMessage(sessionId, ProtoUtil.createCommandCompletedEvent(null)); break; case SHUTDOWN_COMMAND : - myAsyncExecutor.submit(new Runnable() { + myAsyncExecutor.execute(new Runnable() { public void run() { try { cancelAllBuildsAndClearState(); diff --git a/jps/jps-builders/src/org/jetbrains/jps/server/ServerState.java b/jps/jps-builders/src/org/jetbrains/jps/server/ServerState.java index 30ae9c556c1e..d9ee61e73f45 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/server/ServerState.java +++ b/jps/jps-builders/src/org/jetbrains/jps/server/ServerState.java @@ -45,7 +45,7 @@ class ServerState { private final List myGlobalLibraries = new ArrayList(); private volatile String myGlobalEncoding = null; private volatile boolean myKeepTempCachesInMemory = false; - private String myIgnoredFilesPatterns; + private volatile String myIgnoredFilesPatterns; public void setGlobals(List libs, Map pathVars, String globalEncoding, String ignoredFilesPatterns) { synchronized (myConfigurationLock) { @@ -53,7 +53,7 @@ class ServerState { myGlobalLibraries.addAll(libs); myPathVariables.putAll(pathVars); myGlobalEncoding = StringUtil.isEmpty(globalEncoding)? null : globalEncoding; - myIgnoredFilesPatterns = ignoredFilesPatterns; + myIgnoredFilesPatterns = StringUtil.isEmpty(ignoredFilesPatterns)? "" : ignoredFilesPatterns; } } diff --git a/lib/miglayout-swing.jar b/lib/miglayout-swing.jar new file mode 100644 index 000000000000..81b75f3e6bd3 Binary files /dev/null and b/lib/miglayout-swing.jar differ diff --git a/lib/netty-3.3.1.Final.jar b/lib/netty-3.3.1.Final.jar deleted file mode 100644 index a107dc9b3874..000000000000 Binary files a/lib/netty-3.3.1.Final.jar and /dev/null differ diff --git a/lib/netty-3.4.0.Final.jar b/lib/netty-3.4.0.Final.jar new file mode 100644 index 000000000000..eea4d1080ea8 Binary files /dev/null and b/lib/netty-3.4.0.Final.jar differ diff --git a/lib/required_for_dist.txt b/lib/required_for_dist.txt index a4e372026865..67d57ba064e6 100644 --- a/lib/required_for_dist.txt +++ b/lib/required_for_dist.txt @@ -28,6 +28,7 @@ log4j.jar jgoodies-common-1.2.1.jar jgoodies-looks-2.4.2.jar microba.jar +miglayout-swing.jar nanoxml-2.2.3.jar nekohtml-1.9.14.jar org.eclipse.jdt.core_3.5.2.v_981_R35x.jar @@ -45,5 +46,5 @@ xmlrpc-2.0.jar xpp3-1.1.4-min.jar xstream.jar swingx-core-1.6.2.jar -netty-3.3.1.Final.jar +netty-3.4.0.Final.jar protobuf-2.4.1.jar diff --git a/lib/src/miglayout-sources.jar b/lib/src/miglayout-sources.jar new file mode 100644 index 000000000000..328016e31f74 Binary files /dev/null and b/lib/src/miglayout-sources.jar differ diff --git a/lib/src/netty-3.3.1.Final-sources.jar b/lib/src/netty-3.4.0.Final-sources.jar similarity index 58% rename from lib/src/netty-3.3.1.Final-sources.jar rename to lib/src/netty-3.4.0.Final-sources.jar index a29b723be1d7..55fb6aa0b518 100644 Binary files a/lib/src/netty-3.3.1.Final-sources.jar and b/lib/src/netty-3.4.0.Final-sources.jar differ diff --git a/platform/lang-api/src/com/intellij/execution/BeforeRunTask.java b/platform/lang-api/src/com/intellij/execution/BeforeRunTask.java index b1f8120f1465..fad6015d02f0 100644 --- a/platform/lang-api/src/com/intellij/execution/BeforeRunTask.java +++ b/platform/lang-api/src/com/intellij/execution/BeforeRunTask.java @@ -16,15 +16,28 @@ package com.intellij.execution; +import com.intellij.openapi.util.Key; import org.jdom.Element; +import org.jetbrains.annotations.NotNull; /** * @author Eugene Zhuravlev * Date: May 18, 2009 */ -public abstract class BeforeRunTask implements Cloneable{ +public abstract class BeforeRunTask implements Cloneable { + @NotNull + protected final Key myProviderId; private boolean myIsEnabled; + protected BeforeRunTask(@NotNull Key providerId) { + myProviderId = providerId; + } + + @NotNull + public final Key getProviderId() { + return myProviderId; + } + public boolean isEnabled() { return myIsEnabled; } @@ -59,13 +72,13 @@ public abstract class BeforeRunTask implements Cloneable{ if (o == null || getClass() != o.getClass()) return false; BeforeRunTask that = (BeforeRunTask)o; - + if (myProviderId != that.myProviderId) return false; if (myIsEnabled != that.myIsEnabled) return false; return true; } public int hashCode() { - return (myIsEnabled ? 1 : 0); + return 31 * myProviderId.hashCode() + (myIsEnabled ? 1 : 0); } } diff --git a/platform/lang-api/src/com/intellij/execution/BeforeRunTaskProvider.java b/platform/lang-api/src/com/intellij/execution/BeforeRunTaskProvider.java index 2085bcadf15c..eec2d04f2d39 100644 --- a/platform/lang-api/src/com/intellij/execution/BeforeRunTaskProvider.java +++ b/platform/lang-api/src/com/intellij/execution/BeforeRunTaskProvider.java @@ -25,9 +25,13 @@ package com.intellij.execution; import com.intellij.execution.configurations.RunConfiguration; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; import org.jetbrains.annotations.Nullable; +import javax.swing.*; + public abstract class BeforeRunTaskProvider { public static final ExtensionPointName> EXTENSION_POINT_NAME = new ExtensionPointName>("com.intellij.stepsBeforeRunProvider"); @@ -35,9 +39,22 @@ public abstract class BeforeRunTaskProvider { public abstract Key getId(); - public abstract String getDescription(final RunConfiguration runConfiguration, T task); + public abstract String getName(); - public abstract boolean hasConfigurationButton(); + @Nullable + public Icon getIcon() { + return null; + }; + + public abstract String getDescription(T task); + + + @Nullable + public Icon getTaskIcon(T task) { + return null; + } + + public abstract boolean isConfigurable(); /** * @return 'before run' task for the configuration or null, if the task from this provider is not applicable to the specified configuration @@ -50,8 +67,18 @@ public abstract class BeforeRunTaskProvider { */ public abstract boolean configureTask(final RunConfiguration runConfiguration, T task); + public abstract boolean canExecuteTask(RunConfiguration configuration, T task); + public abstract boolean executeTask(DataContext context, RunConfiguration configuration, T task); + /** + * + * @return true if at most one task may be configured + */ + public boolean isSingleton() { + return false; + } + /** * Get runner id that current run is about to be made by * @param context data context that is passed to {@link #executeTask(com.intellij.openapi.actionSystem.DataContext, com.intellij.execution.configurations.RunConfiguration, BeforeRunTask)} @@ -61,4 +88,14 @@ public abstract class BeforeRunTaskProvider { public static String getRunnerId(DataContext context) { return (String)context.getData(RUNNER_ID); } + + @Nullable + public static BeforeRunTaskProvider getProvider(Project project, Key key) { + BeforeRunTaskProvider[] providers = Extensions.getExtensions(BeforeRunTaskProvider.EXTENSION_POINT_NAME, project); + for (BeforeRunTaskProvider provider : providers) { + if (provider.getId() == key) + return (BeforeRunTaskProvider)provider; + } + return null; + } } \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonListeners.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonListeners.java index 53791bb8dec8..92a23259e39b 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonListeners.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonListeners.java @@ -130,7 +130,8 @@ class DaemonListeners implements Disposable { @Override public void caretPositionChanged(CaretEvent e) { Editor editor = e.getEditor(); - if (!editor.getComponent().isShowing() || !worthBothering(editor.getDocument(), editor.getProject())) { + if ((!editor.getComponent().isShowing() && !ApplicationManager.getApplication().isUnitTestMode()) || + !worthBothering(editor.getDocument(), editor.getProject())) { return; //no need to stop daemon if something happened in the console } diff --git a/java/java-impl/src/com/intellij/codeInsight/folding/impl/CollapseExpandJavadocsHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/CollapseExpandDocCommentsHandler.java similarity index 88% rename from java/java-impl/src/com/intellij/codeInsight/folding/impl/CollapseExpandJavadocsHandler.java rename to platform/lang-impl/src/com/intellij/codeInsight/folding/impl/CollapseExpandDocCommentsHandler.java index 91b30142ec00..199acc9159bc 100644 --- a/java/java-impl/src/com/intellij/codeInsight/folding/impl/CollapseExpandJavadocsHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/CollapseExpandDocCommentsHandler.java @@ -20,16 +20,16 @@ import com.intellij.codeInsight.folding.CodeFoldingManager; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.FoldRegion; import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiDocCommentBase; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; -import com.intellij.psi.javadoc.PsiDocComment; import org.jetbrains.annotations.NotNull; -public class CollapseExpandJavadocsHandler implements CodeInsightActionHandler { +public class CollapseExpandDocCommentsHandler implements CodeInsightActionHandler { private final boolean myExpand; - public CollapseExpandJavadocsHandler(boolean isExpand) { + public CollapseExpandDocCommentsHandler(boolean isExpand) { myExpand = isExpand; } @@ -45,7 +45,7 @@ public class CollapseExpandJavadocsHandler implements CodeInsightActionHandler { public void run() { for (FoldRegion region : allFoldRegions) { PsiElement element = EditorFoldingInfo.get(editor).getPsiElement(region); - if (element instanceof PsiDocComment) { + if (element instanceof PsiDocCommentBase) { region.setExpanded(myExpand); } } diff --git a/java/java-impl/src/com/intellij/codeInsight/folding/impl/actions/ExpandJavadocsAction.java b/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/actions/CollapseDocCommentsAction.java similarity index 80% rename from java/java-impl/src/com/intellij/codeInsight/folding/impl/actions/ExpandJavadocsAction.java rename to platform/lang-impl/src/com/intellij/codeInsight/folding/impl/actions/CollapseDocCommentsAction.java index 95c87f3d6db9..38af70944d57 100644 --- a/java/java-impl/src/com/intellij/codeInsight/folding/impl/actions/ExpandJavadocsAction.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/actions/CollapseDocCommentsAction.java @@ -17,11 +17,11 @@ package com.intellij.codeInsight.folding.impl.actions; import com.intellij.codeInsight.CodeInsightActionHandler; import com.intellij.codeInsight.actions.BaseCodeInsightAction; -import com.intellij.codeInsight.folding.impl.CollapseExpandJavadocsHandler; +import com.intellij.codeInsight.folding.impl.CollapseExpandDocCommentsHandler; -public class ExpandJavadocsAction extends BaseCodeInsightAction{ +public class CollapseDocCommentsAction extends BaseCodeInsightAction{ @Override protected CodeInsightActionHandler getHandler(){ - return new CollapseExpandJavadocsHandler(true); + return new CollapseExpandDocCommentsHandler(false); } } diff --git a/java/java-impl/src/com/intellij/codeInsight/folding/impl/actions/CollapseJavadocsAction.java b/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/actions/ExpandDocCommentsAction.java similarity index 80% rename from java/java-impl/src/com/intellij/codeInsight/folding/impl/actions/CollapseJavadocsAction.java rename to platform/lang-impl/src/com/intellij/codeInsight/folding/impl/actions/ExpandDocCommentsAction.java index a5bee9a87c8e..6d2d95ff9ee4 100644 --- a/java/java-impl/src/com/intellij/codeInsight/folding/impl/actions/CollapseJavadocsAction.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/actions/ExpandDocCommentsAction.java @@ -17,11 +17,11 @@ package com.intellij.codeInsight.folding.impl.actions; import com.intellij.codeInsight.CodeInsightActionHandler; import com.intellij.codeInsight.actions.BaseCodeInsightAction; -import com.intellij.codeInsight.folding.impl.CollapseExpandJavadocsHandler; +import com.intellij.codeInsight.folding.impl.CollapseExpandDocCommentsHandler; -public class CollapseJavadocsAction extends BaseCodeInsightAction{ +public class ExpandDocCommentsAction extends BaseCodeInsightAction{ @Override protected CodeInsightActionHandler getHandler(){ - return new CollapseExpandJavadocsHandler(false); + return new CollapseExpandDocCommentsHandler(true); } } diff --git a/platform/lang-impl/src/com/intellij/codeInspection/reference/RefElementImpl.java b/platform/lang-impl/src/com/intellij/codeInspection/reference/RefElementImpl.java index 8097af612960..9071543e5984 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/reference/RefElementImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/reference/RefElementImpl.java @@ -87,6 +87,13 @@ public abstract class RefElementImpl extends RefEntityImpl implements RefElement if (myIsDeleted) return false; return ApplicationManager.getApplication().runReadAction(new Computable() { public Boolean compute() { + + final PsiFile file = myID.getContainingFile(); + //no need to check resolve in offline mode + if (ApplicationManager.getApplication().isHeadlessEnvironment()) { + return file != null && file.isPhysical(); + } + final PsiElement element = getElement(); return element != null && element.isPhysical(); } diff --git a/platform/lang-impl/src/com/intellij/codeInspection/reference/RefManagerImpl.java b/platform/lang-impl/src/com/intellij/codeInspection/reference/RefManagerImpl.java index e7469ec93ff3..e5b1a24a19fa 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/reference/RefManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/reference/RefManagerImpl.java @@ -43,6 +43,7 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectUtil; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.Segment; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; @@ -210,8 +211,8 @@ public class RefManagerImpl extends RefManager { if (refEntity instanceof RefElement) { final RefElement refElement = (RefElement)refEntity; - PsiElement psiElement = refElement.getElement(); - PsiFile psiFile = psiElement.getContainingFile(); + final SmartPsiElementPointer pointer = refElement.getPointer(); + PsiFile psiFile = pointer.getContainingFile(); Element fileElement = new Element("file"); Element lineElement = new Element("line"); @@ -220,9 +221,10 @@ public class RefManagerImpl extends RefManager { fileElement.addContent(virtualFile.getUrl()); if (actualLine == -1) { - final Document document = PsiDocumentManager.getInstance(refElement.getRefManager().getProject()).getDocument(psiFile); + final Document document = PsiDocumentManager.getInstance(pointer.getProject()).getDocument(psiFile); LOG.assertTrue(document != null); - lineElement.addContent(String.valueOf(document.getLineNumber(psiElement.getTextOffset()) + 1)); + final Segment range = pointer.getRange(); + lineElement.addContent(String.valueOf(range != null ? (document.getLineNumber(range.getStartOffset()) + 1) : -1)); } else { lineElement.addContent(String.valueOf(actualLine)); diff --git a/platform/lang-impl/src/com/intellij/execution/RunManagerEx.java b/platform/lang-impl/src/com/intellij/execution/RunManagerEx.java index b221a829268b..714bc7a2317f 100644 --- a/platform/lang-impl/src/com/intellij/execution/RunManagerEx.java +++ b/platform/lang-impl/src/com/intellij/execution/RunManagerEx.java @@ -24,7 +24,7 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.util.Collection; -import java.util.Map; +import java.util.List; /** * Manages {@link RunConfiguration}s. @@ -55,20 +55,23 @@ public abstract class RunManagerEx extends RunManager { @NotNull public abstract RunnerAndConfigurationSettings createConfiguration(String name, ConfigurationFactory type); - public abstract void addConfiguration(RunnerAndConfigurationSettings settings, boolean isShared, Map, BeforeRunTask> tasks); + public abstract void addConfiguration(RunnerAndConfigurationSettings settings, boolean isShared, List tasks); public abstract void addConfiguration(final RunnerAndConfigurationSettings settings, final boolean isShared); public abstract boolean isConfigurationShared(RunnerAndConfigurationSettings settings); @NotNull - public abstract Map, BeforeRunTask> getBeforeRunTasks(RunConfiguration settings); - - @Nullable - public abstract T getBeforeRunTask(RunConfiguration settings, Key taskProviderID); + public abstract List getBeforeRunTasks(RunConfiguration settings); @NotNull - public abstract Collection getBeforeRunTasks(Key taskProviderID, boolean includeOnlyActiveTasks); + public abstract List getBeforeRunTasks(RunConfiguration settings, boolean includeOnlyActiveTasks); + + @NotNull + public abstract List getBeforeRunTasks(RunConfiguration settings, Key taskProviderID); + + @NotNull + public abstract List getBeforeRunTasks(Key taskProviderID, boolean includeOnlyActiveTasks); public abstract RunnerAndConfigurationSettings findConfigurationByName(@Nullable final String name); diff --git a/platform/lang-impl/src/com/intellij/execution/impl/BaseExecuteBeforeRunDialog.java b/platform/lang-impl/src/com/intellij/execution/impl/BaseExecuteBeforeRunDialog.java index 638759607a8a..fe59622cc785 100644 --- a/platform/lang-impl/src/com/intellij/execution/impl/BaseExecuteBeforeRunDialog.java +++ b/platform/lang-impl/src/com/intellij/execution/impl/BaseExecuteBeforeRunDialog.java @@ -39,9 +39,8 @@ import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; -import java.util.ArrayList; -import java.util.Enumeration; -import java.util.Set; +import java.util.*; +import java.util.List; public abstract class BaseExecuteBeforeRunDialog extends DialogWrapper { @@ -180,8 +179,12 @@ public abstract class BaseExecuteBeforeRunDialog extend } private boolean isConfigurationAssigned(RunConfiguration configuration) { - final T task = RunManagerEx.getInstanceEx(myProject).getBeforeRunTask(configuration, getTaskID()); - return task != null && isRunning(task); + final java.util.List tasks = RunManagerEx.getInstanceEx(myProject).getBeforeRunTasks(configuration, getTaskID()); + for (T task : tasks) { + if (isRunning(task)) + return true; + } + return false; } protected void doOKAction() { @@ -215,20 +218,24 @@ public abstract class BaseExecuteBeforeRunDialog extend protected abstract boolean isRunning(T task); private void update(RunConfiguration config, boolean enabled, RunManagerImpl runManager) { - T task = runManager.getBeforeRunTask(config, getTaskID()); - if (task == null) return; - + List tasks = runManager.getBeforeRunTasks(config); + BeforeRunTaskProvider provider = BeforeRunTaskProvider.getProvider(myProject, getTaskID()); + if (provider == null) + return; + T task = provider.createTask(config); + update(task); + task.setEnabled(true); if (enabled) { - task.setEnabled(true); - update(task); + if (!tasks.contains(task)) { + tasks.add(task); + } } else { - if (isRunning(task)) { - task.setEnabled(false); - clear(task); + if (tasks.contains(task)) { + tasks.remove(task); } - // do not change the task otherwise } + runManager.setBeforeRunTasks(config, tasks); } protected abstract void update(T task); diff --git a/platform/lang-impl/src/com/intellij/execution/impl/BeforeRunStepsPanel.java b/platform/lang-impl/src/com/intellij/execution/impl/BeforeRunStepsPanel.java new file mode 100644 index 000000000000..d9a797a8cde1 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/execution/impl/BeforeRunStepsPanel.java @@ -0,0 +1,229 @@ +/* + * Copyright 2000-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.execution.impl; + +import com.intellij.execution.*; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.execution.configurations.UnknownRunConfiguration; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.DefaultActionGroup; +import com.intellij.openapi.actionSystem.impl.SimpleDataContext; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.ui.popup.ListPopup; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.Pair; +import com.intellij.ui.*; +import com.intellij.ui.components.JBList; +import com.intellij.util.containers.hash.HashSet; +import net.miginfocom.swing.MigLayout; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +/** + * @author Vassiliy Kudryashov + */ +class BeforeRunStepsPanel extends JPanel { + private JCheckBox myShowSettingsBeforeRunCheckBox; + private JBList myList; + private final CollectionListModel myModel; + private RunConfiguration myRunConfiguration; + + private final List originalTasks = new ArrayList(); + private StepsBeforeRunListener myListener; + + BeforeRunStepsPanel(StepsBeforeRunListener listener) { + myListener = listener; + myModel = new CollectionListModel(); + myList = new JBList(myModel); + myList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + myList.setCellRenderer(new MyListCellRenderer()); + ToolbarDecorator myDecorator = ToolbarDecorator.createDecorator(myList); + myDecorator.setEditAction(new AnActionButtonRunnable() { + @Override + public void run(AnActionButton button) { + int index = myList.getSelectedIndex(); + if (index ==-1) + return; + Pair> selection = getSelection(); + if (selection == null) + return; + BeforeRunTask task = selection.getFirst(); + BeforeRunTaskProvider provider = selection.getSecond(); + if (provider.configureTask(myRunConfiguration, task)) { + myModel.setElementAt(task, index); + } + } + }); + myDecorator.setEditActionUpdater(new AnActionButtonUpdater() { + @Override + public boolean isEnabled(AnActionEvent e) { + Pair> selection = getSelection(); + return selection != null && selection.getSecond().isConfigurable(); + } + }); + myDecorator.setAddAction(new AnActionButtonRunnable() { + @Override + public void run(AnActionButton button) { + doAddAction(button); + } + }); + + myShowSettingsBeforeRunCheckBox = new JCheckBox(ExecutionBundle.message("configuration.edit.before.run")); + + setLayout(new MigLayout("fill, ins 0, gap 10")); + add(myShowSettingsBeforeRunCheckBox, "shrinky, wrap"); + add(myDecorator.createPanel(), "grow, push"); + } + + @Nullable + private Pair> getSelection() { + final int index = myList.getSelectedIndex(); + if (index ==-1) + return null; + BeforeRunTask task = myModel.getElementAt(index); + Key providerId = task.getProviderId(); + BeforeRunTaskProvider provider = BeforeRunTaskProvider.getProvider(myRunConfiguration.getProject(), providerId); + return provider != null ? Pair.create(task, provider) : null; + } + + void doReset(RunnerAndConfigurationSettings settings) { + myRunConfiguration = settings.getConfiguration(); + + originalTasks.clear(); + originalTasks.addAll(RunManagerImpl.getInstanceImpl(myRunConfiguration.getProject()).getBeforeRunTasks(myRunConfiguration, true)); + myModel.replaceAll(originalTasks); + myShowSettingsBeforeRunCheckBox.setSelected(settings.isEditBeforeRun()); + myShowSettingsBeforeRunCheckBox.setEnabled(!(myRunConfiguration instanceof UnknownRunConfiguration)); + } + + public List getTasks(boolean applyCurrentState) { + if (applyCurrentState) { + originalTasks.clear(); + originalTasks.addAll(myModel.getItems()); + } + return Collections.unmodifiableList(originalTasks); + } + + public boolean needEditBeforeRun() { + return myShowSettingsBeforeRunCheckBox.isSelected(); + } + + void doAddAction(AnActionButton button) { + if (myRunConfiguration instanceof UnknownRunConfiguration) { + return; + } + + final JBPopupFactory popupFactory = JBPopupFactory.getInstance(); + final BeforeRunTaskProvider[] providers = Extensions.getExtensions(BeforeRunTaskProvider.EXTENSION_POINT_NAME, + myRunConfiguration.getProject()); + final List activeProviderKeys = getActiveProviderKeys(); + + DefaultActionGroup actionGroup = new DefaultActionGroup(null, false); + for (final BeforeRunTaskProvider provider : providers) { + String providerName = provider.getName(); + if (provider.createTask(myRunConfiguration) == null) + continue; + if (activeProviderKeys.contains(provider.getId()) && provider.isSingleton()) + continue; + AnAction providerAction = new AnAction(providerName, null, provider.getIcon()) { + @Override + public void actionPerformed(AnActionEvent e) { + BeforeRunTask task = provider.createTask(myRunConfiguration); + if (task != null) { + provider.configureTask(myRunConfiguration, task); + if (!provider.canExecuteTask(myRunConfiguration, task)) + return; + } else { + return; + } + task.setEnabled(true); + + Set configurationSet = new HashSet(); + getAllRunBeforeRuns(task, configurationSet); + if (configurationSet.contains(myRunConfiguration)) { + JOptionPane.showMessageDialog(BeforeRunStepsPanel.this, + ExecutionBundle.message("before.launch.panel.cyclic_dependency_warning", + myRunConfiguration.getName(), + provider.getDescription(task)), + ExecutionBundle.message("warning.common.title"),JOptionPane.WARNING_MESSAGE); + return; + } + myModel.add(task); + myListener.fireStepsBeforeRunChanged(); + } + }; + actionGroup.add(providerAction); + } + final ListPopup popup = + popupFactory.createActionGroupPopup(ExecutionBundle.message("add.new.run.configuration.acrtion.name"), actionGroup, + SimpleDataContext.getProjectContext(myRunConfiguration.getProject()), false, false, false, null, + -1, Condition.TRUE); + popup.show(button.getPreferredPopupPoint()); + } + + private List getActiveProviderKeys() { + List result = new ArrayList(); + for (BeforeRunTask task : myModel.getItems()) { + result.add(task.getProviderId()); + } + return result; + } + + private void getAllRunBeforeRuns(BeforeRunTask task, Set configurationSet) { + if (task instanceof RunConfigurationBeforeRunProvider.RunConfigurableBeforeRunTask) { + RunConfigurationBeforeRunProvider.RunConfigurableBeforeRunTask runTask + = (RunConfigurationBeforeRunProvider.RunConfigurableBeforeRunTask)task; + RunConfiguration configuration = runTask.getSettings().getConfiguration(); + + List tasks = RunManagerImpl.getInstanceImpl(configuration.getProject()).getBeforeRunTasks(configuration, true); + for (BeforeRunTask beforeRunTask : tasks) { + if (beforeRunTask instanceof RunConfigurationBeforeRunProvider.RunConfigurableBeforeRunTask) { + configurationSet.add(((RunConfigurationBeforeRunProvider.RunConfigurableBeforeRunTask)beforeRunTask).getSettings().getConfiguration()); + getAllRunBeforeRuns(beforeRunTask, configurationSet); + } + } + } + } + + interface StepsBeforeRunListener { + void fireStepsBeforeRunChanged(); + } + + private class MyListCellRenderer extends JBList.StripedListCellRenderer { + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if (value instanceof BeforeRunTask) { + BeforeRunTask task = (BeforeRunTask)value; + BeforeRunTaskProvider provider = BeforeRunTaskProvider.getProvider(myRunConfiguration.getProject(), task.getProviderId()); + if (provider != null) { + setIcon(provider.getTaskIcon(task)); + setText(provider.getDescription(task)); + } + } + return this; + } + } +} diff --git a/platform/lang-impl/src/com/intellij/execution/impl/ConfigurationSettingsEditorWrapper.form b/platform/lang-impl/src/com/intellij/execution/impl/ConfigurationSettingsEditorWrapper.form index 8973059b4e7d..89bbe4fdf05f 100644 --- a/platform/lang-impl/src/com/intellij/execution/impl/ConfigurationSettingsEditorWrapper.form +++ b/platform/lang-impl/src/com/intellij/execution/impl/ConfigurationSettingsEditorWrapper.form @@ -17,13 +17,13 @@ - + - + diff --git a/platform/lang-impl/src/com/intellij/execution/impl/ConfigurationSettingsEditorWrapper.java b/platform/lang-impl/src/com/intellij/execution/impl/ConfigurationSettingsEditorWrapper.java index 6a8b5f35018d..dd93b12e5e12 100644 --- a/platform/lang-impl/src/com/intellij/execution/impl/ConfigurationSettingsEditorWrapper.java +++ b/platform/lang-impl/src/com/intellij/execution/impl/ConfigurationSettingsEditorWrapper.java @@ -17,8 +17,6 @@ package com.intellij.execution.impl; import com.intellij.execution.BeforeRunTask; -import com.intellij.execution.BeforeRunTaskProvider; -import com.intellij.execution.ExecutionBundle; import com.intellij.execution.RunnerAndConfigurationSettings; import com.intellij.execution.configurations.RunConfiguration; import com.intellij.execution.configurations.UnknownRunConfiguration; @@ -27,48 +25,43 @@ import com.intellij.ide.impl.TypeSafeDataProviderAdapter; import com.intellij.openapi.actionSystem.DataKey; import com.intellij.openapi.actionSystem.DataSink; import com.intellij.openapi.actionSystem.TypeSafeDataProvider; -import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.options.SettingsEditor; -import com.intellij.openapi.ui.FixedSizeButton; import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.Key; -import com.intellij.util.ui.UIUtil; -import gnu.trove.THashMap; +import net.miginfocom.swing.MigLayout; import org.jetbrains.annotations.NotNull; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Map; /** * User: anna * Date: 27-Mar-2006 */ -public class ConfigurationSettingsEditorWrapper extends SettingsEditor { +public class ConfigurationSettingsEditorWrapper extends SettingsEditor + implements BeforeRunStepsPanel.StepsBeforeRunListener { public static DataKey CONFIGURATION_EDITOR_KEY = DataKey.create("ConfigurationSettingsEditor"); private JPanel myComponentPlace; private JCheckBox myCbStoreProjectConfiguration; private JPanel myWholePanel; - private JPanel myStepsPanel; - private JCheckBox myShowSettingsBeforeRunCheckBox; - private Map, BeforeRunTask> myStepsBeforeLaunch; - private final Map, StepBeforeLaunchRow> myStepBeforeLaunchRows = new THashMap, StepBeforeLaunchRow>(); + private JPanel myBeforeLaunchContainer; + private BeforeRunStepsPanel myBeforeRunStepsPanel; private boolean myStoreProjectConfiguration; - private boolean myEditBeforeRun; private final ConfigurationSettingsEditor myEditor; public ConfigurationSettingsEditorWrapper(final RunnerAndConfigurationSettings settings) { myEditor = new ConfigurationSettingsEditor(settings); Disposer.register(this, myEditor); + myBeforeRunStepsPanel = new BeforeRunStepsPanel(this); + myBeforeLaunchContainer.setLayout(new MigLayout("fill, ins 0")); + myBeforeLaunchContainer.add(myBeforeRunStepsPanel, "grow, push"); doReset(settings); } @@ -76,54 +69,8 @@ public class ConfigurationSettingsEditorWrapper extends SettingsEditor[] providers = Extensions.getExtensions(BeforeRunTaskProvider.EXTENSION_POINT_NAME, - runConfiguration.getProject()); - myStepsPanel.removeAll(); - if (runConfiguration instanceof UnknownRunConfiguration) { - myStepsPanel.setVisible(false); - } - else { - List stepsRows = new ArrayList(); - for (BeforeRunTaskProvider provider : providers) { - final BeforeRunTask task = myStepsBeforeLaunch.get(provider.getId()); - if (task != null) { - final StepBeforeLaunchRow stepRow = new StepBeforeLaunchRow(runConfiguration, provider, task); - myStepBeforeLaunchRows.put(provider.getId(), stepRow); - stepsRows.add(stepRow); - } - } - - int maxStepRowWidth = 0; - for (StepBeforeLaunchRow stepRow : stepsRows) { - maxStepRowWidth = Math.max(maxStepRowWidth, stepRow.getPreferredSize().width); - } - if (maxStepRowWidth * 3 < getComponent().getPreferredSize().width || maxStepRowWidth < 200) { - myStepsPanel.setLayout(new GridLayout(0, 3, UIUtil.DEFAULT_HGAP, UIUtil.DEFAULT_VGAP)); - } else if (maxStepRowWidth * 2 < getComponent().getPreferredSize().width || maxStepRowWidth < 300) { - myStepsPanel.setLayout(new GridLayout(0, 2, UIUtil.DEFAULT_HGAP, UIUtil.DEFAULT_VGAP)); - } else { - myStepsPanel.setLayout(new GridLayout(0, 1, UIUtil.DEFAULT_HGAP, UIUtil.DEFAULT_VGAP)); - } - for (StepBeforeLaunchRow stepRow : stepsRows) { - myStepsPanel.add(stepRow); - } - } - - myEditBeforeRun = settings.isEditBeforeRun(); - myShowSettingsBeforeRunCheckBox = new JCheckBox(ExecutionBundle.message("configuration.edit.before.run")); - myShowSettingsBeforeRunCheckBox.setEnabled(!(runConfiguration instanceof UnknownRunConfiguration)); - myShowSettingsBeforeRunCheckBox.setSelected(myEditBeforeRun); - myShowSettingsBeforeRunCheckBox.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - myEditBeforeRun = myShowSettingsBeforeRunCheckBox.isSelected(); - } - }); - myShowSettingsBeforeRunCheckBox.setVisible(!settings.isTemplate()); - myStepsPanel.add(myShowSettingsBeforeRunCheckBox); + myBeforeRunStepsPanel.doReset(settings); + myBeforeLaunchContainer.setVisible(!(runConfiguration instanceof UnknownRunConfiguration)); myStoreProjectConfiguration = runManager.isConfigurationShared(settings); myCbStoreProjectConfiguration.setEnabled(!(runConfiguration instanceof UnknownRunConfiguration)); @@ -141,15 +88,10 @@ public class ConfigurationSettingsEditorWrapper extends SettingsEditor key) { - myStepBeforeLaunchRows.get(key).update(myStepsBeforeLaunch.get(key)); - } - protected void disposeEditor() { } @@ -172,71 +114,22 @@ public class ConfigurationSettingsEditorWrapper extends SettingsEditor, BeforeRunTask> getStepsBeforeLaunch() { - return Collections.unmodifiableMap(myStepsBeforeLaunch); + public List getStepsBeforeLaunch() { + return Collections.unmodifiableList(myBeforeRunStepsPanel.getTasks(false)); } public boolean isStoreProjectConfiguration() { return myStoreProjectConfiguration; } - private class StepBeforeLaunchRow extends JPanel { - private final JCheckBox myCheckBox; - private FixedSizeButton myButton; - private final RunConfiguration myRunConfiguration; - private final BeforeRunTaskProvider myProvider; - - public StepBeforeLaunchRow(final RunConfiguration runConfiguration, final BeforeRunTaskProvider provider, - final BeforeRunTask beforeRunTask) { - super(new GridBagLayout()); - myRunConfiguration = runConfiguration; - myProvider = provider; - final boolean isChecked = beforeRunTask.isEnabled(); - myCheckBox = new JCheckBox(provider.getDescription(runConfiguration, beforeRunTask), isChecked); - GridBagConstraints gc = new GridBagConstraints(GridBagConstraints.RELATIVE, 0 , 1, 1, 0, 1, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0,0,0,0), 0, 0); - add(myCheckBox, gc); - gc.weightx = 1; - if (provider.hasConfigurationButton()) { - myButton = new FixedSizeButton(20); - add(myButton, gc); - - myButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - if (provider.configureTask(runConfiguration, beforeRunTask)) { - myCheckBox.setText(provider.getDescription(runConfiguration, beforeRunTask)); - fireEditorStateChanged(); - } - } - }); - } - else { - add(Box.createHorizontalBox(), gc); - } - enableSettings(beforeRunTask); - myCheckBox.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - beforeRunTask.setEnabled(myCheckBox.isSelected()); - enableSettings(beforeRunTask); - } - }); - } - - private void enableSettings(final BeforeRunTask task) { - if (myButton != null) { - myButton.setEnabled(myCheckBox.isSelected()); - } - myCheckBox.setText(myProvider.getDescription(myRunConfiguration, task)); - } - - public void update(BeforeRunTask task) { - myCheckBox.setSelected(task.isEnabled()); - enableSettings(task); - } + @Override + public void fireStepsBeforeRunChanged() { + fireEditorStateChanged(); } private class MyDataProvider implements TypeSafeDataProvider { diff --git a/platform/lang-impl/src/com/intellij/execution/impl/ExecutionManagerImpl.java b/platform/lang-impl/src/com/intellij/execution/impl/ExecutionManagerImpl.java index d2672ff88fc1..f15e9fa2c222 100644 --- a/platform/lang-impl/src/com/intellij/execution/impl/ExecutionManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/execution/impl/ExecutionManagerImpl.java @@ -31,7 +31,6 @@ import com.intellij.openapi.actionSystem.impl.SimpleDataContext; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.ProjectComponent; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Disposer; @@ -41,9 +40,7 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.util.ArrayList; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; /** * @author dyoma @@ -99,24 +96,25 @@ public class ExecutionManagerImpl extends ExecutionManager implements ProjectCom final RunConfiguration runConfiguration = (RunConfiguration)configuration; final RunManagerImpl runManager = RunManagerImpl.getInstanceImpl(myProject); - final Map, BeforeRunTask> activeProviders = new LinkedHashMap, BeforeRunTask>(); - for (final BeforeRunTaskProvider provider : Extensions.getExtensions(BeforeRunTaskProvider.EXTENSION_POINT_NAME, myProject)) { - final BeforeRunTask task = runManager.getBeforeRunTask(runConfiguration, provider.getId()); - if (task != null && task.isEnabled()) { - activeProviders.put(provider, task); + final List activeTasks = new ArrayList(); + final List tasks = runManager.getBeforeRunTasks(runConfiguration); + for (BeforeRunTask task : tasks) { + if (task != null && task.isEnabled()) { + activeTasks.add(task); + } } - } ConfigurationPerRunnerSettings configurationSettings = state != null ? state.getConfigurationSettings() : null; final DataContext projectContext = SimpleDataContext.getProjectContext(myProject); final DataContext dataContext = configurationSettings != null ? SimpleDataContext .getSimpleContext(BeforeRunTaskProvider.RUNNER_ID, configurationSettings.getRunnerId(), projectContext) : projectContext; - if (!activeProviders.isEmpty()) { + if (!activeTasks.isEmpty()) { ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { public void run() { - for (BeforeRunTaskProvider provider : activeProviders.keySet()) { - if(!provider.executeTask(dataContext, runConfiguration, activeProviders.get(provider))) { + for (BeforeRunTask task : activeTasks) { + BeforeRunTaskProvider provider = BeforeRunTaskProvider.getProvider(myProject, task.getProviderId()); + if(provider != null && !provider.executeTask(dataContext, runConfiguration, task)) { if (onCancelRunnable != null) { SwingUtilities.invokeLater(onCancelRunnable); } diff --git a/platform/lang-impl/src/com/intellij/execution/impl/RunConfigurable.java b/platform/lang-impl/src/com/intellij/execution/impl/RunConfigurable.java index 4963327c22db..8eea1482dfaf 100644 --- a/platform/lang-impl/src/com/intellij/execution/impl/RunConfigurable.java +++ b/platform/lang-impl/src/com/intellij/execution/impl/RunConfigurable.java @@ -33,7 +33,6 @@ import com.intellij.openapi.ui.popup.PopupStep; import com.intellij.openapi.ui.popup.util.BaseListPopupStep; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.IconLoader; -import com.intellij.openapi.util.Key; import com.intellij.openapi.wm.IdeFocusManager; import com.intellij.ui.*; import com.intellij.ui.components.JBScrollPane; @@ -65,6 +64,7 @@ import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.net.URL; import java.util.*; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -895,7 +895,7 @@ class RunConfigurable extends BaseConfigurable { }); final ListPopup popup = popupFactory.createListPopup(new BaseListPopupStep( - ExecutionBundle.message("add.new.run.configuration.acrtion.name"), configurationTypes) { + ExecutionBundle.message("add.new.run.configuration.acrtion.name"), configurationTypes) { @NotNull public String getTextFor(final ConfigurationType type) { @@ -953,7 +953,7 @@ class RunConfigurable extends BaseConfigurable { } }); return new BaseListPopupStep( - ExecutionBundle.message("add.new.run.configuration.action.name", type.getDisplayName()), factories) { + ExecutionBundle.message("add.new.run.configuration.action.name", type.getDisplayName()), factories) { @NotNull public String getTextFor(final ConfigurationFactory value) { @@ -1168,15 +1168,15 @@ class RunConfigurable extends BaseConfigurable { private static class RunConfigurationBean { private final RunnerAndConfigurationSettings mySettings; private final boolean myShared; - private final Map, BeforeRunTask> myStepsBeforeLaunch; + private final List myStepsBeforeLaunch; private final SingleConfigurationConfigurable myConfigurable; public RunConfigurationBean(final RunnerAndConfigurationSettings settings, final boolean shared, - final Map, BeforeRunTask> stepsBeforeLaunch) { + final List stepsBeforeLaunch) { mySettings = settings; myShared = shared; - myStepsBeforeLaunch = Collections.unmodifiableMap(stepsBeforeLaunch); + myStepsBeforeLaunch = Collections.unmodifiableList(stepsBeforeLaunch); myConfigurable = null; } @@ -1196,7 +1196,7 @@ class RunConfigurable extends BaseConfigurable { return myShared; } - public Map, BeforeRunTask> getStepsBeforeLaunch() { + public List getStepsBeforeLaunch() { return myStepsBeforeLaunch; } diff --git a/platform/lang-impl/src/com/intellij/execution/impl/RunConfigurationBeforeRunProvider.java b/platform/lang-impl/src/com/intellij/execution/impl/RunConfigurationBeforeRunProvider.java new file mode 100644 index 000000000000..5aa825b75ca8 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/execution/impl/RunConfigurationBeforeRunProvider.java @@ -0,0 +1,347 @@ +/* + * Copyright 2000-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.execution.impl; + +import com.intellij.execution.*; +import com.intellij.execution.configurations.ConfigurationType; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.execution.executors.DefaultRunExecutor; +import com.intellij.execution.process.ProcessAdapter; +import com.intellij.execution.process.ProcessEvent; +import com.intellij.execution.process.ProcessHandler; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.runners.ProgramRunner; +import com.intellij.execution.ui.RunContentDescriptor; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.util.Key; +import com.intellij.ui.ColoredListCellRenderer; +import com.intellij.ui.SimpleTextAttributes; +import com.intellij.ui.components.JBList; +import com.intellij.ui.components.JBScrollPane; +import com.intellij.util.concurrency.Semaphore; +import org.jdom.Attribute; +import org.jdom.Element; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +/** + * @author Vassiliy Kudryashov + */ +public class RunConfigurationBeforeRunProvider +extends BeforeRunTaskProvider { + private static final Icon ICON = IconLoader.getIcon("/actions/execute.png"); + + public static final Key ID = Key.create("RunConfigurationTask"); + + private static final Logger LOG = Logger.getInstance("#com.intellij.execution.impl.RunConfigurationBeforeRunProvider"); + + private final Project myProject; + + public RunConfigurationBeforeRunProvider(Project project) { + myProject = project; + } + + @Override + public Key getId() { + return ID; + } + + @Override + public Icon getIcon() { + return ICON; + } + + @Override + public Icon getTaskIcon(RunConfigurableBeforeRunTask task) { + if (task.getSettings() == null) + return null; + return ProgramRunnerUtil.getConfigurationIcon(myProject, task.getSettings(), false); + } + + @Override + public String getName() { + return ExecutionBundle.message("before.launch.run.another.configuration"); + } + + @Override + public String getDescription(RunConfigurableBeforeRunTask task) { + if (task.getSettings() == null) { + return ExecutionBundle.message("before.launch.run.another.configuration"); + } + else { + return ExecutionBundle.message("before.launch.run.certain.configuration", task.getSettings().getName()); + } + } + + @Override + public boolean isConfigurable() { + return true; + } + + @Override + public RunConfigurableBeforeRunTask createTask(RunConfiguration runConfiguration) { + return new RunConfigurableBeforeRunTask(); + } + + @Override + public boolean configureTask(RunConfiguration runConfiguration, RunConfigurableBeforeRunTask task) { + final RunManagerImpl runManager = RunManagerImpl.getInstanceImpl(runConfiguration.getProject()); + + final ArrayList configurations + = new ArrayList(runManager.getSortedConfigurations()); + String executorId = DefaultRunExecutor.getRunExecutorInstance().getId(); + for (Iterator iterator = configurations.iterator(); iterator.hasNext();) { + RunnerAndConfigurationSettings settings = iterator.next(); + final ProgramRunner runner = ProgramRunnerUtil.getRunner(executorId, settings); + if (runner == null) + iterator.remove(); + } + + SelectionDialog dialog = + new SelectionDialog(task.getSettings(), configurations); + dialog.show(); + RunnerAndConfigurationSettings settings = dialog.getSelectedSettings(); + if (settings != null) { + task.setSettings(settings); + return true; + } + else { + return false; + } + } + + @Override + public boolean canExecuteTask(RunConfiguration configuration, + RunConfigurableBeforeRunTask task) { + RunnerAndConfigurationSettings settings = task.getSettings(); + if (settings == null) { + return false; + } + String executorId = DefaultRunExecutor.getRunExecutorInstance().getId(); + final ProgramRunner runner = ProgramRunnerUtil.getRunner(executorId, settings); + if (runner == null) + return false; + final ExecutionEnvironment environment = new ExecutionEnvironment(runner, settings, myProject); + return runner.canRun(executorId, environment.getRunProfile()); + } + + @Override + public boolean executeTask(final DataContext dataContext, RunConfiguration configuration, RunConfigurableBeforeRunTask task) { + RunnerAndConfigurationSettings settings = task.getSettings(); + if (settings == null) { + return false; + } + final Executor executor = DefaultRunExecutor.getRunExecutorInstance(); + String executorId = executor.getId(); + final ProgramRunner runner = ProgramRunnerUtil.getRunner(executorId, settings); + if (runner == null) + return false; + final ExecutionEnvironment environment = new ExecutionEnvironment(runner, settings, myProject); + if (!runner.canRun(executorId, environment.getRunProfile())) { + return false; + } + else { + final Semaphore targetDone = new Semaphore(); + final boolean[] result = new boolean[1]; + try { + ApplicationManager.getApplication().invokeAndWait(new Runnable() { + + public void run() { + targetDone.down(); + try { + runner.execute(executor, environment, new ProgramRunner.Callback() { + @Override + public void processStarted(RunContentDescriptor descriptor) { + ProcessHandler processHandler = descriptor != null ? descriptor.getProcessHandler() : null; + if (processHandler != null) { + processHandler.addProcessListener(new ProcessAdapter() { + @Override + public void processTerminated(ProcessEvent event) { + result[0] = event.getExitCode() == 0; + targetDone.up(); + } + }); + } + } + }); + } + catch (ExecutionException e) { + LOG.error(e); + } + } + }, ModalityState.NON_MODAL); + } + catch (Exception e) { + LOG.error(e); + return false; + } + targetDone.waitFor(); + return result[0]; + } + } + + class RunConfigurableBeforeRunTask extends BeforeRunTask { + private String myConfigurationName; + private String myConfigurationType; + private boolean myInitialized = false; + + private RunnerAndConfigurationSettings mySettings; + + RunConfigurableBeforeRunTask() { + super(ID); + } + + @Override + public void writeExternal(Element element) { + super.writeExternal(element); + if (myConfigurationName != null && myConfigurationType != null) { + element.setAttribute("run_configuration_name", myConfigurationName); + element.setAttribute("run_configuration_type", myConfigurationType); + } + else if (mySettings != null) { + element.setAttribute("run_configuration_name", mySettings.getName()); + element.setAttribute("run_configuration_type", mySettings.getType().getId()); + } + } + + @Override + public void readExternal(Element element) { + super.readExternal(element); + Attribute configurationNameAttr = element.getAttribute("run_configuration_name"); + Attribute configurationTypeAttr = element.getAttribute("run_configuration_type"); + myConfigurationName = configurationNameAttr != null ? configurationNameAttr.getValue() : null; + myConfigurationType = configurationTypeAttr != null ? configurationTypeAttr.getValue() : null; + } + + void init() { + if (myInitialized) { + return; + } + if (myConfigurationName != null && myConfigurationType != null) { + Collection configurations = RunManagerImpl.getInstanceImpl(myProject).getSortedConfigurations(); + for (RunnerAndConfigurationSettings runConfiguration : configurations) { + ConfigurationType type = runConfiguration.getType(); + if (myConfigurationName.equals(runConfiguration.getName()) + && type != null + && myConfigurationType.equals(type.getId())) { + setSettings(runConfiguration); + return; + } + } + } + } + + void setSettings(RunnerAndConfigurationSettings settings) { + mySettings = settings; + myInitialized = true; + } + + RunnerAndConfigurationSettings getSettings() { + init(); + return mySettings; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + + RunConfigurableBeforeRunTask that = (RunConfigurableBeforeRunTask)o; + + if (myConfigurationName != null ? !myConfigurationName.equals(that.myConfigurationName) : that.myConfigurationName != null) return false; + if (myConfigurationType != null ? !myConfigurationType.equals(that.myConfigurationType) : that.myConfigurationType != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (myConfigurationName != null ? myConfigurationName.hashCode() : 0); + result = 31 * result + (myConfigurationType != null ? myConfigurationType.hashCode() : 0); + return result; + } + } + + private class SelectionDialog extends DialogWrapper { + private RunnerAndConfigurationSettings mySelectedSettings; + @NotNull private final List mySettings; + private JBList myJBList; + + private SelectionDialog(RunnerAndConfigurationSettings selectedSettings, @NotNull List settings) { + super(myProject); + setTitle("Choose Configuration to Execute"); + mySelectedSettings = selectedSettings; + mySettings = settings; + init(); + myJBList.setSelectedValue(mySelectedSettings, true); + } + + @Override + protected JComponent createCenterPanel() { + myJBList = new JBList(mySettings); + myJBList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + myJBList.getSelectionModel().addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { + Object selectedValue = myJBList.getSelectedValue(); + if (selectedValue instanceof RunnerAndConfigurationSettings) { + mySelectedSettings = (RunnerAndConfigurationSettings)selectedValue; + } + else { + mySelectedSettings = null; + } + setOKActionEnabled(mySelectedSettings != null); + } + }); + myJBList.setCellRenderer(new ColoredListCellRenderer() { + @Override + protected void customizeCellRenderer(JList list, Object value, int index, boolean selected, boolean hasFocus) { + if (value instanceof RunnerAndConfigurationSettings) { + RunnerAndConfigurationSettings settings = (RunnerAndConfigurationSettings)value; + RunManagerEx runManager = RunManagerEx.getInstanceEx(myProject); + setIcon(runManager.getConfigurationIcon(settings)); + RunConfiguration configuration = settings.getConfiguration(); + append(configuration.getName(), runManager.isTemporary(configuration) + ? SimpleTextAttributes.GRAY_ATTRIBUTES + : SimpleTextAttributes.REGULAR_ATTRIBUTES); + } + } + }); + return new JBScrollPane(myJBList); + } + + @Nullable + RunnerAndConfigurationSettings getSelectedSettings() { + return mySelectedSettings; + } + } +} diff --git a/platform/lang-impl/src/com/intellij/execution/impl/RunManagerImpl.java b/platform/lang-impl/src/com/intellij/execution/impl/RunManagerImpl.java index 1bebd81a8414..0061cdad34f7 100644 --- a/platform/lang-impl/src/com/intellij/execution/impl/RunManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/execution/impl/RunManagerImpl.java @@ -48,11 +48,7 @@ public class RunManagerImpl extends RunManagerEx implements JDOMExternalizable, private final Map myConfigurations = new LinkedHashMap(); // template configurations are not included here private final Map mySharedConfigurations = new TreeMap(); - /** - * configurationID -> [BeforeTaskProviderName->BeforeRunTask] - */ - private final Map, BeforeRunTask>> myConfigurationToBeforeTasksMap = - new WeakHashMap, BeforeRunTask>>(); + private final Map> myConfigurationToBeforeTasksMap = new WeakHashMap>(); // When readExternal not all configuration may be loaded, so we need to remember the selected configuration // so that when it is eventually loaded, we can mark is as a selected. @@ -244,7 +240,7 @@ public class RunManagerImpl extends RunManagerEx implements JDOMExternalizable, public void addConfiguration(RunnerAndConfigurationSettings settings, boolean shared, - Map, BeforeRunTask> tasks) { + List tasks) { final RunConfiguration configuration = settings.getConfiguration(); Integer existingId = findConfigurationIdByUniqueName(getUniqueName(settings)); @@ -441,35 +437,12 @@ public class RunManagerImpl extends RunManagerEx implements JDOMExternalizable, ((RunnerAndConfigurationSettingsImpl)settings).writeExternal(configurationElement); if (!(settings.getConfiguration() instanceof UnknownRunConfiguration)) { - final Map, BeforeRunTask> tasks = getBeforeRunTasks(settings.getConfiguration()); - Map, BeforeRunTask> templateTasks; - if (!settings.isTemplate()) { - final RunnerAndConfigurationSettings templateSettings = getConfigurationTemplate(settings.getFactory()); - templateTasks = getBeforeRunTasks(templateSettings.getConfiguration()); - } - else { - templateTasks = null; - } - final List> order = new ArrayList>(tasks.keySet()); - Collections.sort(order, new Comparator>() { - public int compare(Key o1, Key o2) { - return o1.toString().compareToIgnoreCase(o2.toString()); - } - }); + final List tasks = getBeforeRunTasks(settings.getConfiguration()); final Element methodsElement = new Element(METHOD); - for (Key providerID : order) { - final BeforeRunTask beforeRunTask = tasks.get(providerID); - - if (templateTasks != null) { - final BeforeRunTask templateTask = templateTasks.get(providerID); - if (beforeRunTask.equals(templateTask)) { - continue; // not neccesary saving if the task is the same as template - } - } - + for (BeforeRunTask task : tasks) { final Element child = new Element(OPTION); - child.setAttribute(NAME_ATTR, providerID.toString()); - beforeRunTask.writeExternal(child); + child.setAttribute(NAME_ATTR, task.getProviderId().toString()); + task.writeExternal(child); methodsElement.addContent(child); } configurationElement.addContent(methodsElement); @@ -575,13 +548,13 @@ public class RunManagerImpl extends RunManagerEx implements JDOMExternalizable, } final Element methodsElement = element.getChild(METHOD); - final Map, BeforeRunTask> map = readStepsBeforeRun(methodsElement, settings); + final List tasks = readStepsBeforeRun(methodsElement, settings); if (settings.isTemplate()) { myTemplateConfigurationsMap.put(factory.getType().getId() + "." + factory.getName(), settings); - setBeforeRunTasks(settings.getConfiguration(), map); + setBeforeRunTasks(settings.getConfiguration(), tasks); } else { - addConfiguration(settings, isShared, map); + addConfiguration(settings, isShared, tasks); if (Boolean.valueOf(element.getAttributeValue(SELECTED_ATTR)).booleanValue()) { //to support old style setSelectedConfiguration(settings); } @@ -590,9 +563,8 @@ public class RunManagerImpl extends RunManagerEx implements JDOMExternalizable, } @NotNull - private Map, BeforeRunTask> readStepsBeforeRun(final Element child, - RunnerAndConfigurationSettings settings) { - final Map, BeforeRunTask> map = new HashMap, BeforeRunTask>(); + private List readStepsBeforeRun(final Element child, RunnerAndConfigurationSettings settings) { + final List result = new ArrayList(); if (child != null) { for (Object o : child.getChildren(OPTION)) { final Element methodElement = (Element)o; @@ -602,11 +574,11 @@ public class RunManagerImpl extends RunManagerEx implements JDOMExternalizable, final BeforeRunTask beforeRunTask = provider.createTask(settings.getConfiguration()); if (beforeRunTask != null) { beforeRunTask.readExternal(methodElement); - map.put(id, beforeRunTask); + result.add(beforeRunTask); } } } - return map; + return result; } @@ -730,22 +702,27 @@ public class RunManagerImpl extends RunManagerEx implements JDOMExternalizable, return shared != null && shared.booleanValue(); } - public Collection getBeforeRunTasks(Key taskProviderID, boolean includeOnlyActiveTasks) { - final Collection tasks = new ArrayList(); + @NotNull + public List getBeforeRunTasks(Key taskProviderID, boolean includeOnlyActiveTasks) { + final List tasks = new ArrayList(); if (includeOnlyActiveTasks) { - final Set checkedTemplates = new HashSet(); + final List checkedTemplates = new ArrayList(); for (RunnerAndConfigurationSettings settings : myConfigurations.values()) { - final BeforeRunTask runTask = getBeforeRunTask(settings.getConfiguration(), taskProviderID); - if (runTask != null && runTask.isEnabled()) { - tasks.add((T)runTask); - } - else { - final RunnerAndConfigurationSettings template = getConfigurationTemplate(settings.getFactory()); - if (!checkedTemplates.contains(template)) { - checkedTemplates.add(template); - final BeforeRunTask templateTask = getBeforeRunTask(template.getConfiguration(), taskProviderID); - if (templateTask != null && templateTask.isEnabled()) { - tasks.add((T)templateTask); + final List runTasks = getBeforeRunTasks(settings.getConfiguration()); + for (BeforeRunTask task : runTasks) { + if (task != null && task.isEnabled() && task.getProviderId() == taskProviderID) { + tasks.add((T)task); + } + else { + final RunnerAndConfigurationSettings template = getConfigurationTemplate(settings.getFactory()); + if (!checkedTemplates.contains(template)) { + checkedTemplates.add(template); + final List templateTasks = getBeforeRunTasks(template.getConfiguration()); + for (BeforeRunTask templateTask : templateTasks) { + if (templateTask != null && templateTask.isEnabled() && templateTask.getProviderId() == taskProviderID) { + tasks.add((T)templateTask); + } + } } } } @@ -753,15 +730,17 @@ public class RunManagerImpl extends RunManagerEx implements JDOMExternalizable, } else { for (RunnerAndConfigurationSettings settings : myTemplateConfigurationsMap.values()) { - final T task = getBeforeRunTask(settings.getConfiguration(), taskProviderID); - if (task != null) { - tasks.add(task); + final List tasks_ = getBeforeRunTasks(settings.getConfiguration()); + for (BeforeRunTask task : tasks_) { + if (task != null && task.getProviderId() == taskProviderID) + tasks.add((T)task); } } for (RunnerAndConfigurationSettings settings : myConfigurations.values()) { - final T task = getBeforeRunTask(settings.getConfiguration(), taskProviderID); - if (task != null) { - tasks.add(task); + final List tasks_ = getBeforeRunTasks(settings.getConfiguration()); + for (BeforeRunTask task : tasks_) { + if (task != null && task.getProviderId() == taskProviderID) + tasks.add((T)task); } } } @@ -811,49 +790,64 @@ public class RunManagerImpl extends RunManagerEx implements JDOMExternalizable, return null; } - @Nullable - public T getBeforeRunTask(RunConfiguration settings, Key taskProviderID) { - Map, BeforeRunTask> tasks = myConfigurationToBeforeTasksMap.get(settings); - if (tasks == null) { - tasks = getBeforeRunTasks(settings); - myConfigurationToBeforeTasksMap.put(settings, tasks); + @NotNull + @Override + public List getBeforeRunTasks(RunConfiguration settings, Key taskProviderID) { + List tasks = getBeforeRunTasks(settings); + List result = new ArrayList(); + for (BeforeRunTask task : tasks) { + if (task.getProviderId() == taskProviderID) + result.add((T)task); } - return (T)tasks.get(taskProviderID); + return result; } - public Map, BeforeRunTask> getBeforeRunTasks(final RunConfiguration settings) { - final Map, BeforeRunTask> tasks = myConfigurationToBeforeTasksMap.get(settings); + @NotNull + public List getBeforeRunTasks(final RunConfiguration settings) { + return getBeforeRunTasks(settings, false); + } + @NotNull + @Override + public List getBeforeRunTasks(RunConfiguration settings, boolean includeOnlyActiveTasks) { + final List tasks = myConfigurationToBeforeTasksMap.get(settings); if (tasks != null) { - final Map, BeforeRunTask> _tasks = new HashMap, BeforeRunTask>(); - for (Map.Entry, BeforeRunTask> entry : tasks.entrySet()) { - _tasks.put(entry.getKey(), entry.getValue().clone()); - } - return _tasks; + return getCopies(tasks, includeOnlyActiveTasks); } final RunnerAndConfigurationSettings template = getConfigurationTemplate(settings.getFactory()); - final Map, BeforeRunTask> templateTasks = myConfigurationToBeforeTasksMap.get(template.getConfiguration()); + final List templateTasks = myConfigurationToBeforeTasksMap.get(template.getConfiguration()); if (templateTasks != null) { - final Map, BeforeRunTask> _tasks = new HashMap, BeforeRunTask>(); - for (Map.Entry, BeforeRunTask> entry : templateTasks.entrySet()) { - _tasks.put(entry.getKey(), entry.getValue().clone()); - } - return _tasks; + return getCopies(templateTasks, includeOnlyActiveTasks); } - final Map, BeforeRunTask> _tasks = new HashMap, BeforeRunTask>(); + final List _tasks = new ArrayList(); for (BeforeRunTaskProvider provider : Extensions .getExtensions(BeforeRunTaskProvider.EXTENSION_POINT_NAME, myProject)) { BeforeRunTask task = provider.createTask(settings); if (task != null) { + if (includeOnlyActiveTasks && !task.isEnabled()) + continue; Key providerID = provider.getId(); - _tasks.put(providerID, task); + _tasks.add(task); settings.getFactory().configureBeforeRunTaskDefaults(providerID, task); } } return _tasks; } + @NotNull + private static List getCopies(List original, boolean includeOnlyActiveTasks) { + List result = new ArrayList(); + if (original != null) { + for (BeforeRunTask task : original) { + if (includeOnlyActiveTasks && !task.isEnabled()) + continue; + result.add(task.clone()); + } + } + return result; + } + public void shareConfiguration(final RunConfiguration runConfiguration, final boolean shareConfiguration) { RunnerAndConfigurationSettings settings = getSettings(runConfiguration); boolean shouldFire = settings != null && isConfigurationShared(settings) != shareConfiguration; @@ -864,14 +858,8 @@ public class RunManagerImpl extends RunManagerEx implements JDOMExternalizable, if (shouldFire) fireRunConfigurationChanged(settings); } - public final void setBeforeRunTasks(final RunConfiguration runConfiguration, Map, BeforeRunTask> tasks) { - final Map, BeforeRunTask> taskMap = getBeforeRunTasks(runConfiguration); - for (Map.Entry, BeforeRunTask> entry : tasks.entrySet()) { - //if (taskMap.containsKey(entry.getKey())) { - taskMap.put(entry.getKey(), entry.getValue()); - //} - } - myConfigurationToBeforeTasksMap.put(runConfiguration, taskMap); + public final void setBeforeRunTasks(final RunConfiguration runConfiguration, List tasks) { + myConfigurationToBeforeTasksMap.put(runConfiguration, tasks); fireBeforeRunTasksUpdated(); } @@ -881,7 +869,7 @@ public class RunManagerImpl extends RunManagerEx implements JDOMExternalizable, } public void addConfiguration(final RunnerAndConfigurationSettings settings, final boolean isShared) { - addConfiguration(settings, isShared, Collections., BeforeRunTask>emptyMap()); + addConfiguration(settings, isShared, new ArrayList()); } public static RunManagerImpl getInstanceImpl(final Project project) { @@ -958,8 +946,8 @@ public class RunManagerImpl extends RunManagerEx implements JDOMExternalizable, } private void initProviderMaps() { - myBeforeStepsMap = new HashMap, BeforeRunTaskProvider>(); - myProviderKeysMap = new HashMap>(); + myBeforeStepsMap = new LinkedHashMap, BeforeRunTaskProvider>(); + myProviderKeysMap = new LinkedHashMap>(); for (BeforeRunTaskProvider provider : Extensions .getExtensions(BeforeRunTaskProvider.EXTENSION_POINT_NAME, myProject)) { final Key id = provider.getId(); diff --git a/platform/lang-impl/src/com/intellij/execution/impl/RunnerAndConfigurationSettingsImpl.java b/platform/lang-impl/src/com/intellij/execution/impl/RunnerAndConfigurationSettingsImpl.java index 8cadadbb19d5..5f3116c907f2 100644 --- a/platform/lang-impl/src/com/intellij/execution/impl/RunnerAndConfigurationSettingsImpl.java +++ b/platform/lang-impl/src/com/intellij/execution/impl/RunnerAndConfigurationSettingsImpl.java @@ -368,7 +368,8 @@ public class RunnerAndConfigurationSettingsImpl implements JDOMExternalizable, C @Override public String toString() { - return getType() + ": " + getName(); + ConfigurationType type = getType(); + return (type != null ? type.getDisplayName() + ": " : "" ) + getName(); } private class InfoProvider implements ConfigurationInfoProvider { diff --git a/platform/lang-impl/src/com/intellij/execution/impl/UnknownBeforeRunTaskProvider.java b/platform/lang-impl/src/com/intellij/execution/impl/UnknownBeforeRunTaskProvider.java index 0b2a224ba23b..e5854b8bfccd 100644 --- a/platform/lang-impl/src/com/intellij/execution/impl/UnknownBeforeRunTaskProvider.java +++ b/platform/lang-impl/src/com/intellij/execution/impl/UnknownBeforeRunTaskProvider.java @@ -18,6 +18,7 @@ package com.intellij.execution.impl; import com.intellij.execution.BeforeRunTask; import com.intellij.execution.BeforeRunTaskProvider; +import com.intellij.execution.ExecutionBundle; import com.intellij.execution.configurations.RunConfiguration; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.util.JDOMUtil; @@ -42,30 +43,42 @@ public class UnknownBeforeRunTaskProvider extends BeforeRunTaskProvider { private Element myConfig; - public UnknownTask() { + public UnknownTask(Key providerId) { + super(providerId); } public void readExternal(Element element) { diff --git a/platform/lang-impl/src/com/intellij/find/impl/FindInProjectUtil.java b/platform/lang-impl/src/com/intellij/find/impl/FindInProjectUtil.java index e073957689ed..a9c2eb118461 100644 --- a/platform/lang-impl/src/com/intellij/find/impl/FindInProjectUtil.java +++ b/platform/lang-impl/src/com/intellij/find/impl/FindInProjectUtil.java @@ -393,7 +393,7 @@ public class FindInProjectUtil { public boolean processFile(@NotNull VirtualFile virtualFile) { if (!virtualFile.isDirectory() && (fileMaskRegExp == null || fileMaskRegExp.matcher(virtualFile.getName()).matches()) && - customScope.contains(virtualFile)) { + (customScope == null || customScope.contains(virtualFile))) { final PsiFile psiFile = psiManager.findFile(virtualFile); if (psiFile != null && !filesForFastWordSearch.contains(psiFile)) { myFiles.add(psiFile); @@ -411,7 +411,7 @@ public class FindInProjectUtil { if (psiDirectory == null) { boolean success = fileIndex.iterateContent(iterator); - if (success && customScope.isSearchInLibraries()) { + if (success && customScope != null && customScope.isSearchInLibraries()) { OrderEnumerator enumerator = module == null ? OrderEnumerator.orderEntries(project) : OrderEnumerator.orderEntries(module); final VirtualFile[] librarySources = enumerator.withoutModuleSourceEntries().withoutDepModules().getSourceRoots(); iterateAll(librarySources, customScope, iterator); @@ -451,14 +451,11 @@ public class FindInProjectUtil { return true; } - @NotNull + @Nullable private static GlobalSearchScope toGlobal(@NotNull Project project, @Nullable SearchScope scope) { - if (scope instanceof GlobalSearchScope) { + if (scope instanceof GlobalSearchScope || scope == null) { return (GlobalSearchScope)scope; } - if (scope == null) { - return projectContentScope(project); - } Set files = new HashSet(); for (PsiElement element : ((LocalSearchScope)scope).getScope()) { PsiFile file = element.getContainingFile(); @@ -471,8 +468,8 @@ public class FindInProjectUtil { @NotNull private static Pair> getFilesForFastWordSearch(@NotNull final FindModel findModel, @NotNull final Project project, - @Nullable final PsiDirectory psiDirectory, final Pattern fileMaskRegExp, - @Nullable final Module module) { + @Nullable final PsiDirectory psiDirectory, final Pattern fileMaskRegExp, + @Nullable final Module module) { if (DumbService.getInstance(project).isDumb()) { return new Pair>(false, Collections.emptyList()); } @@ -480,13 +477,16 @@ public class FindInProjectUtil { PsiManager pm = PsiManager.getInstance(project); CacheManager cacheManager = CacheManager.SERVICE.getInstance(project); SearchScope customScope = findModel.getCustomScope(); - @NotNull GlobalSearchScope scope = psiDirectory != null - ? GlobalSearchScopes.directoryScope(psiDirectory, true) - : module != null - ? moduleContentScope(module) - : customScope instanceof GlobalSearchScope - ? (GlobalSearchScope)customScope - : toGlobal(project, customScope); + GlobalSearchScope scope = psiDirectory != null + ? GlobalSearchScopes.directoryScope(psiDirectory, true) + : module != null + ? moduleContentScope(module) + : customScope instanceof GlobalSearchScope + ? (GlobalSearchScope)customScope + : toGlobal(project, customScope); + if (scope == null) { + scope = GlobalSearchScope.projectScope(project); + } Set keys = new THashSet(30); Set resultFiles = new THashSet(); @@ -554,16 +554,7 @@ public class FindInProjectUtil { return new Pair>(fast, resultFiles); } - private static GlobalSearchScope projectContentScope(final Project project) { - GlobalSearchScope result = null; - for (Module module : ModuleManager.getInstance(project).getModules()) { - GlobalSearchScope moduleContent = moduleContentScope(module); - result = result == null ? moduleContent : result.uniteWith(moduleContent); - } - return result == null ? GlobalSearchScope.EMPTY_SCOPE : result; - } - - @Nullable + @NotNull private static GlobalSearchScope moduleContentScope(@NotNull final Module module) { VirtualFile[] contentRoots = ModuleRootManager.getInstance(module).getContentRoots(); GlobalSearchScope result = null; diff --git a/platform/lang-impl/src/com/intellij/openapi/roots/impl/ProjectRootManagerImpl.java b/platform/lang-impl/src/com/intellij/openapi/roots/impl/ProjectRootManagerImpl.java index db298eddd1f6..517d2ca07783 100644 --- a/platform/lang-impl/src/com/intellij/openapi/roots/impl/ProjectRootManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/openapi/roots/impl/ProjectRootManagerImpl.java @@ -614,12 +614,7 @@ public class ProjectRootManagerImpl extends ProjectRootManagerEx implements Proj private void addRootsToWatch() { final Set rootPaths = getAllRoots(false); if (rootPaths == null) return; - - final Set newRootsToWatch = LocalFileSystem.getInstance().addRootsToWatch(rootPaths, true); - - //remove old requests after adding new ones, helps avoiding unnecessary synchronizations - LocalFileSystem.getInstance().removeWatchedRoots(myRootsToWatch); - myRootsToWatch = newRootsToWatch; + myRootsToWatch = LocalFileSystem.getInstance().replaceWatchedRoots(myRootsToWatch, rootPaths, true); } @Nullable diff --git a/platform/lang-impl/src/com/intellij/openapi/roots/impl/libraries/JarDirectoryWatcher.java b/platform/lang-impl/src/com/intellij/openapi/roots/impl/libraries/JarDirectoryWatcher.java index 4be3f5234824..b59388e101d0 100644 --- a/platform/lang-impl/src/com/intellij/openapi/roots/impl/libraries/JarDirectoryWatcher.java +++ b/platform/lang-impl/src/com/intellij/openapi/roots/impl/libraries/JarDirectoryWatcher.java @@ -1,3 +1,18 @@ +/* + * Copyright 2000-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.intellij.openapi.roots.impl.libraries; import com.intellij.openapi.Disposable; @@ -12,17 +27,15 @@ import com.intellij.openapi.vfs.newvfs.events.*; import com.intellij.util.messages.MessageBusConnection; import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; -import java.util.List; +import java.util.*; /** * @author ksafonov */ public abstract class JarDirectoryWatcher implements Disposable { - private final JarDirectories myJarDirectories; private MessageBusConnection myBusConnection = null; - private final List myWatchRequests = new ArrayList(); + private Collection myWatchRequests = Collections.emptySet(); public JarDirectoryWatcher(JarDirectories jarDirectories) { myJarDirectories = jarDirectories; @@ -30,21 +43,39 @@ public abstract class JarDirectoryWatcher implements Disposable { public void updateWatchedRoots() { final LocalFileSystem fs = LocalFileSystem.getInstance(); - if (!myWatchRequests.isEmpty()) { - fs.removeWatchedRoots(myWatchRequests); - myWatchRequests.clear(); - } if (!myJarDirectories.isEmpty()) { + final Set recursiveRoots = new HashSet(); + final Set flatRoots = new HashSet(); final VirtualFileManager fm = VirtualFileManager.getInstance(); for (OrderRootType rootType : myJarDirectories.getRootTypes()) { for (String url : myJarDirectories.getDirectories(rootType)) { if (fm.getFileSystem(VirtualFileManager.extractProtocol(url)) instanceof LocalFileSystem) { final boolean watchRecursively = myJarDirectories.isRecursive(rootType, url); - final LocalFileSystem.WatchRequest request = fs.addRootToWatch(VirtualFileManager.extractPath(url), watchRecursively); - myWatchRequests.add(request); + final String path = VirtualFileManager.extractPath(url); + (watchRecursively ? recursiveRoots : flatRoots).add(path); } } } + + if (flatRoots.isEmpty()) { + myWatchRequests = fs.replaceWatchedRoots(myWatchRequests, recursiveRoots, true); + } + else if (recursiveRoots.isEmpty()) { + myWatchRequests = fs.replaceWatchedRoots(myWatchRequests, flatRoots, false); + } + else { + fs.removeWatchedRoots(myWatchRequests); + final int rootsTotal = flatRoots.size() + recursiveRoots.size(); + if (rootsTotal > 0) { + myWatchRequests = new ArrayList(rootsTotal); + myWatchRequests.addAll(fs.addRootsToWatch(flatRoots, false)); + myWatchRequests.addAll(fs.addRootsToWatch(recursiveRoots, true)); + } + else { + myWatchRequests = Collections.emptySet(); + } + } + if (myBusConnection == null) { myBusConnection = ApplicationManager.getApplication().getMessageBus().connect(); myBusConnection.subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener() { @@ -58,17 +89,18 @@ public abstract class JarDirectoryWatcher implements Disposable { for (VFileEvent event : events) { if (event instanceof VFileCopyEvent) { final VFileCopyEvent copyEvent = (VFileCopyEvent)event; + final VirtualFile file = copyEvent.getFile(); if (isUnderJarDirectory(copyEvent.getNewParent() + "/" + copyEvent.getNewChildName()) || - isUnderJarDirectory(copyEvent.getFile().getUrl())) { + file != null && isUnderJarDirectory(file.getUrl())) { changesDetected = true; break; } } else if (event instanceof VFileMoveEvent) { final VFileMoveEvent moveEvent = (VFileMoveEvent)event; - final VirtualFile file = moveEvent.getFile(); - if (isUnderJarDirectory(file.getUrl()) || isUnderJarDirectory(moveEvent.getOldParent().getUrl() + "/" + file.getName())) { + if (file != null && + (isUnderJarDirectory(file.getUrl()) || isUnderJarDirectory(moveEvent.getOldParent().getUrl() + "/" + file.getName()))) { changesDetected = true; break; } @@ -106,11 +138,7 @@ public abstract class JarDirectoryWatcher implements Disposable { } } else { - final MessageBusConnection connection = myBusConnection; - if (connection != null) { - myBusConnection = null; - connection.disconnect(); - } + cleanup(); } } @@ -118,13 +146,19 @@ public abstract class JarDirectoryWatcher implements Disposable { @Override public void dispose() { + cleanup(); + } + + private void cleanup() { if (!myWatchRequests.isEmpty()) { LocalFileSystem.getInstance().removeWatchedRoots(myWatchRequests); - myWatchRequests.clear(); + myWatchRequests = Collections.emptySet(); } - if (myBusConnection != null) { - myBusConnection.disconnect(); + + final MessageBusConnection connection = myBusConnection; + if (connection != null) { myBusConnection = null; + connection.disconnect(); } } } diff --git a/platform/lang-impl/src/com/intellij/refactoring/lang/ExtractIncludeDialog.java b/platform/lang-impl/src/com/intellij/refactoring/lang/ExtractIncludeDialog.java index 4e5aae8824f2..c3a0556e5848 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/lang/ExtractIncludeDialog.java +++ b/platform/lang-impl/src/com/intellij/refactoring/lang/ExtractIncludeDialog.java @@ -121,7 +121,8 @@ public class ExtractIncludeDialog extends DialogWrapper { private void validateOKButton() { final String fileName = myNameField.getText().trim(); setOKActionEnabled(myTargetDirectoryField.getText().trim().length() > 0 && - fileName.length() > 0 && fileName.indexOf(File.separatorChar) < 0 && !StringUtil.containsAnyChar(fileName, "*?><\":;|")); + fileName.length() > 0 && fileName.indexOf(File.separatorChar) < 0 && + !StringUtil.containsAnyChar(fileName, "*?><\":;|") && fileName.indexOf(".") != 0); } private static boolean isFileExist(@NotNull final String directory, @NotNull final String fileName) { diff --git a/platform/platform-api/src/com/intellij/openapi/ui/ComboBox.java b/platform/platform-api/src/com/intellij/openapi/ui/ComboBox.java index 0f655992bc4b..8ca24bc16c44 100644 --- a/platform/platform-api/src/com/intellij/openapi/ui/ComboBox.java +++ b/platform/platform-api/src/com/intellij/openapi/ui/ComboBox.java @@ -99,18 +99,21 @@ public class ComboBox extends ComboBoxWithWidePopup implements AWTEventListener if (value != null) { configureEditor(getEditor(), value); IdeFocusManager.getGlobalInstance().requestFocus(ComboBox.this, true); + assert myJBPopup != null; + ComboBox.this.getUI().setPopupVisible(ComboBox.this, false); + myJBPopup.cancel(); } } }) + .setFocusOwners(new Component[]{this}) .setMinSize(new Dimension(getWidth(), -1)) - .setMovable(false) - .setResizable(false) .createPopup(); list.setBorder(IdeBorderFactory.createEmptyBorder(0)); myJBPopup.showUnderneathOf(this); list.addFocusListener(new FocusAdapter() { @Override public void focusLost(FocusEvent e) { + ComboBox.this.getUI().setPopupVisible(ComboBox.this, false); myJBPopup.cancel(); } }); @@ -167,8 +170,10 @@ public class ComboBox extends ComboBoxWithWidePopup implements AWTEventListener public void removeNotify() { super.removeNotify(); Toolkit.getDefaultToolkit().removeAWTEventListener(this); - if (myJBPopup != null && myJBPopup.isVisible()) { + if (myJBPopup != null) { + getUI().setPopupVisible(this, false); myJBPopup.cancel(); + } } diff --git a/platform/platform-api/src/com/intellij/openapi/vfs/LocalFileSystem.java b/platform/platform-api/src/com/intellij/openapi/vfs/LocalFileSystem.java index 82420a7cb661..900c009995ad 100644 --- a/platform/platform-api/src/com/intellij/openapi/vfs/LocalFileSystem.java +++ b/platform/platform-api/src/com/intellij/openapi/vfs/LocalFileSystem.java @@ -25,8 +25,11 @@ import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; import java.util.Collection; +import java.util.Collections; import java.util.Set; +import static java.util.Collections.singleton; + public abstract class LocalFileSystem extends NewVirtualFileSystem { @NonNls public static final String PROTOCOL = "file"; @NonNls public static final String PROTOCOL_PREFIX = PROTOCOL + "://"; @@ -125,14 +128,34 @@ public abstract class LocalFileSystem extends NewVirtualFileSystem { } @Nullable - public abstract WatchRequest addRootToWatch(@NotNull final String rootPath, final boolean toWatchRecursively); + public WatchRequest addRootToWatch(@NotNull final String rootPath, final boolean watchRecursively) { + final Set result = addRootsToWatch(singleton(rootPath), watchRecursively); + return result.size() == 1 ? result.iterator().next() : null; + } @NotNull - public abstract Set addRootsToWatch(@NotNull final Collection rootPaths, final boolean toWatchRecursively); + public abstract Set addRootsToWatch(@NotNull final Collection rootPaths, final boolean watchRecursively); - public abstract void removeWatchedRoots(@NotNull final Collection rootsToWatch); + public void removeWatchedRoot(@Nullable final WatchRequest watchRequest) { + if (watchRequest != null) { + removeWatchedRoots(singleton(watchRequest)); + } + } - public abstract void removeWatchedRoot(@NotNull final WatchRequest watchRequest); + public abstract void removeWatchedRoots(@NotNull final Collection watchRequests); + + @Nullable + public WatchRequest replaceWatchedRoot(@Nullable final WatchRequest watchRequest, + @NotNull final String rootPath, + final boolean watchRecursively) { + final Set requests = watchRequest != null ? singleton(watchRequest) : Collections.emptySet(); + final Set result = replaceWatchedRoots(requests, singleton(rootPath), watchRecursively); + return result.size() == 1 ? result.iterator().next() : null; + } + + public abstract Set replaceWatchedRoots(@NotNull final Collection watchRequests, + @NotNull final Collection rootPaths, + final boolean watchRecursively); public abstract void registerAuxiliaryFileOperationsHandler(@NotNull LocalFileOperationsHandler handler); diff --git a/platform/platform-api/src/com/intellij/ui/CollectionListModel.java b/platform/platform-api/src/com/intellij/ui/CollectionListModel.java index 1d48a44a0e3d..161529538472 100644 --- a/platform/platform-api/src/com/intellij/ui/CollectionListModel.java +++ b/platform/platform-api/src/com/intellij/ui/CollectionListModel.java @@ -50,6 +50,8 @@ public class CollectionListModel extends AbstractListModel { } public void add(@NotNull final List elements) { + if (elements.isEmpty()) + return; int i = myItems.size(); myItems.addAll(elements); fireIntervalAdded(this, i, i + elements.size() - 1); @@ -93,8 +95,7 @@ public class CollectionListModel extends AbstractListModel { } public void replaceAll(@NotNull final List elements) { - myItems.clear(); - myItems.addAll(elements); - fireIntervalAdded(this, 0, elements.size() - 1); + removeAll(); + add(elements); } } diff --git a/platform/platform-api/src/com/intellij/ui/ListUtil.java b/platform/platform-api/src/com/intellij/ui/ListUtil.java index 383a16e50a21..437988e1889f 100644 --- a/platform/platform-api/src/com/intellij/ui/ListUtil.java +++ b/platform/platform-api/src/com/intellij/ui/ListUtil.java @@ -168,9 +168,29 @@ public class ListUtil { if (model instanceof FilteringListModel) { return (DefaultListModel)((FilteringListModel)model).getOriginalModel(); } + if (model instanceof CollectionListModel) { + return getWrapperModel(((CollectionListModel)model)); + } return (DefaultListModel)model; } + private static DefaultListModel getWrapperModel(final CollectionListModel source) { + DefaultListModel model = new DefaultListModel() { + @Override + public Object set(int index, Object element) { + Object o = source.getElementAt(index); + source.setElementAt(element, index); + return o; + } + + @Override + public Object get(int index) { + return source.getElementAt(index); + } + }; + return model; + } + public static boolean canMoveSelectedItemsDown(JList list) { ListModel model = list.getModel(); int[] indices = list.getSelectedIndices(); diff --git a/platform/platform-api/src/com/intellij/ui/components/JBList.java b/platform/platform-api/src/com/intellij/ui/components/JBList.java index d511265209d9..e73b6c0558d9 100644 --- a/platform/platform-api/src/com/intellij/ui/components/JBList.java +++ b/platform/platform-api/src/com/intellij/ui/components/JBList.java @@ -22,6 +22,7 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.ui.ComponentWithExpandableItems; import com.intellij.ui.ExpandableItemsHandler; import com.intellij.ui.ExpandableItemsHandlerFactory; +import com.intellij.ui.table.JBTable; import com.intellij.util.ArrayUtil; import com.intellij.util.NotNullFunction; import com.intellij.util.ui.AsyncProcessIcon; @@ -225,4 +226,15 @@ public class JBList extends JList implements ComponentWithEmptyText, ComponentWi public void disableEmptyText() { getEmptyText().setText(""); } + + public static class StripedListCellRenderer extends DefaultListCellRenderer { + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if (!isSelected && index % 2 == 0) { + setBackground(JBTable.DECORATED_ROW_BG_COLOR); + } + return this; + } + } } diff --git a/platform/platform-api/src/com/intellij/ui/table/JBTable.java b/platform/platform-api/src/com/intellij/ui/table/JBTable.java index d221c9a91d72..f8265bea7c4b 100644 --- a/platform/platform-api/src/com/intellij/ui/table/JBTable.java +++ b/platform/platform-api/src/com/intellij/ui/table/JBTable.java @@ -41,7 +41,7 @@ import java.util.Comparator; import java.util.EventObject; public class JBTable extends JTable implements ComponentWithEmptyText, ComponentWithExpandableItems { - private static final Color DECORATED_ROW_BG_COLOR = new Color(242, 245, 249); + public static final Color DECORATED_ROW_BG_COLOR = new Color(242, 245, 249); private StatusText myEmptyText; private ExpandableItemsHandler myExpandableItemsHandler; diff --git a/platform/platform-impl/platform-impl.iml b/platform/platform-impl/platform-impl.iml index e505978e2610..11055551a29d 100644 --- a/platform/platform-impl/platform-impl.iml +++ b/platform/platform-impl/platform-impl.iml @@ -28,6 +28,7 @@ + diff --git a/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.java b/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.java index ed706726980e..c90db82b0f9e 100644 --- a/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.java +++ b/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2012 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -287,7 +287,7 @@ public class IdeEventQueue extends EventQueue { return myDispatchers.contains(dispatcher); } - public void addPostprocessor(EventDispatcher dispatcher, Disposable parent) { + public void addPostprocessor(EventDispatcher dispatcher, @Nullable Disposable parent) { _addProcessor(dispatcher, parent, myPostprocessors); } diff --git a/platform/platform-impl/src/com/intellij/ide/actions/Switcher.java b/platform/platform-impl/src/com/intellij/ide/actions/Switcher.java index a34ab39eb415..0a63f7899462 100644 --- a/platform/platform-impl/src/com/intellij/ide/actions/Switcher.java +++ b/platform/platform-impl/src/com/intellij/ide/actions/Switcher.java @@ -31,7 +31,10 @@ import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.popup.JBPopup; import com.intellij.openapi.ui.popup.JBPopupFactory; -import com.intellij.openapi.util.*; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.Iconable; +import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.FileStatus; import com.intellij.openapi.vcs.FileStatusManager; @@ -42,7 +45,6 @@ import com.intellij.openapi.wm.ToolWindowManager; import com.intellij.openapi.wm.ex.WindowManagerEx; import com.intellij.openapi.wm.impl.IdeFrameImpl; import com.intellij.openapi.wm.impl.ToolWindowManagerImpl; -import com.intellij.openapi.wm.impl.content.GraphicsConfig; import com.intellij.ui.*; import com.intellij.ui.components.JBList; import com.intellij.ui.speedSearch.NameFilteringListModel; @@ -142,25 +144,25 @@ public class Switcher extends AnAction implements DumbAware { public void actionPerformed(AnActionEvent e) { final Project project = PlatformDataKeys.PROJECT.getData(e.getDataContext()); if (project == null) return; - boolean selectFirstItem = false; if (SWITCHER == null) { synchronized (Switcher.class) { if (SWITCHER == null) { SWITCHER = createAndShowSwitcher(project, SWITCHER_TITLE, false); FeatureUsageTracker.getInstance().triggerFeatureUsed(SWITCHER_FEATURE_ID); - selectFirstItem = !FileEditorManagerEx.getInstanceEx(project).hasOpenedFile(); } } } assert SWITCHER != null; - if (e.getInputEvent().isShiftDown()) { - SWITCHER.goBack(); - } else { - if (selectFirstItem) { - SWITCHER.files.setSelectedIndex(0); + if (!SWITCHER.isPinnedMode()) { + if (e.getInputEvent().isShiftDown()) { + SWITCHER.goBack(); } else { - SWITCHER.goForward(); + if (!FileEditorManagerEx.getInstanceEx(project).hasOpenedFile()) { + SWITCHER.files.setSelectedIndex(0); + } else { + SWITCHER.goForward(); + } } } } @@ -247,7 +249,7 @@ public class Switcher extends AnAction implements DumbAware { public boolean value(String s) { return !mySpeedSearch.isPopupActive() || StringUtil.isEmpty(mySpeedSearch.getEnteredPrefix()) - || mySpeedSearch.matchingFragments(s) != null; + || mySpeedSearch.getComparator().matchingFragments(mySpeedSearch.getEnteredPrefix(), s) != null; } }, mySpeedSearch); } @@ -291,6 +293,7 @@ public class Switcher extends AnAction implements DumbAware { }; separator.setBackground(Color.WHITE); + int selectionIndex = -1; final FileEditorManagerImpl editorManager = (FileEditorManagerImpl)FileEditorManager.getInstance(project); final ArrayList filesData = new ArrayList(); final ArrayList editors = new ArrayList(); @@ -320,12 +323,13 @@ public class Switcher extends AnAction implements DumbAware { filesData.add(info); if (!firstRecentMarked) { firstRecentMarked = true; - info.isSeparator = true; + selectionIndex = filesData.size() - 1; } } } if (editors.size() == 1 && (filesData.isEmpty() || !editors.get(0).getFirst().equals(filesData.get(0).getFirst()))) { filesData.add(0, editors.get(0)); + selectionIndex--; } } else { for (int i = 0; i < Math.min(MAX_FILES_IN_SWITCHER, editors.size()); i++) { @@ -338,22 +342,7 @@ public class Switcher extends AnAction implements DumbAware { filesModel.addElement(editor); } - final VirtualFilesRenderer filesRenderer = new VirtualFilesRenderer(project, mySpeedSearch) { - @Override - public Component getListCellRendererComponent(JList list, - Object value, - int index, - boolean selected, - boolean hasFocus) { - final JComponent renderer = (JComponent)super.getListCellRendererComponent(list, value, index, selected, hasFocus); - if (selected) { - return renderer; - } - final Color bgColor = list == mouseMoveSrc && index == mouseMoveListIndex ? ON_MOUSE_OVER_BG_COLOR : Color.WHITE; - UIUtil.changeBackGround(renderer, bgColor); - return renderer; - } - }; + final VirtualFilesRenderer filesRenderer = new VirtualFilesRenderer(project); final ListSelectionListener filesSelectionListener = new ListSelectionListener() { private String getTitle2Text(String fullText) { @@ -403,7 +392,7 @@ public class Switcher extends AnAction implements DumbAware { public boolean value(String s) { return !mySpeedSearch.isPopupActive() || StringUtil.isEmpty(mySpeedSearch.getEnteredPrefix()) - || mySpeedSearch.matchingFragments(s) != null; + || mySpeedSearch.getComparator().matchingFragments(mySpeedSearch.getEnteredPrefix(), s) != null; } }, mySpeedSearch); } @@ -437,6 +426,9 @@ public class Switcher extends AnAction implements DumbAware { } else { this.add(files, BorderLayout.EAST); } + if (selectionIndex > -1) { + files.setSelectedIndex(selectionIndex); + } this.add(separator, BorderLayout.CENTER); } this.add(descriptions, BorderLayout.SOUTH); @@ -554,6 +546,12 @@ public class Switcher extends AnAction implements DumbAware { case VK_ESCAPE: cancel(); break; + case VK_PAGE_DOWN: + ListScrollingUtil.movePageDown(getSelectedList()); + break; + case VK_PAGE_UP: + ListScrollingUtil.movePageUp(getSelectedList()); + break; case VK_DELETE: case VK_BACK_SPACE: // Mac users closeTabOrToolWindow(); @@ -886,49 +884,30 @@ public class Switcher extends AnAction implements DumbAware { private static class VirtualFilesRenderer extends ColoredListCellRenderer { private final Project myProject; - private final SpeedSearchBase mySearch; - private boolean hide = false; - private boolean separator = false; - public VirtualFilesRenderer(Project project, SpeedSearchBase search) { + public VirtualFilesRenderer(Project project) { myProject = project; - mySearch = search; } protected void customizeCellRenderer(JList list, Object value, int index, boolean selected, boolean hasFocus) { - hide = false; if (value instanceof FileInfo) { - final VirtualFile virtualFile = ((FileInfo)value).first; - final String name = virtualFile.getPresentableName(); + VirtualFile virtualFile = ((FileInfo)value).getFirst(); + String name = virtualFile.getPresentableName(); setIcon(IconUtil.getIcon(virtualFile, Iconable.ICON_FLAG_READ_STATUS, myProject)); - separator = ((FileInfo)value).isSeparator; - if ( mySearch != null && mySearch.isPopupActive()) { - hide = mySearch.matchingFragments(name) == null && !StringUtil.isEmpty(mySearch.getEnteredPrefix()); - } - - final FileStatus fileStatus = FileStatusManager.getInstance(myProject).getStatus(virtualFile); - final TextAttributes attributes = new TextAttributes(fileStatus.getColor(), null, null, EffectType.LINE_UNDERSCORE, Font.PLAIN); + FileStatus fileStatus = FileStatusManager.getInstance(myProject).getStatus(virtualFile); + TextAttributes attributes = new TextAttributes(fileStatus.getColor(), null , null, EffectType.LINE_UNDERSCORE, + Font.PLAIN); append(name, SimpleTextAttributes.fromTextAttributes(attributes)); - } - } - @Override - protected void doPaint(Graphics2D g) { - GraphicsConfig config = new GraphicsConfig(g); - if (hide) { - g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.15f)); - } - super.doPaint(g); - config.restore(); - if (separator) { - UIUtil.drawDottedLine(g, 0, 0, getWidth(), 0, Color.WHITE, Color.BLACK); + if (!selected && FileEditorManager.getInstance(myProject).isFileOpen(virtualFile)) { + setBackground(LightColors.SLIGHTLY_GREEN); + } } } } private static class FileInfo extends Pair { - boolean isSeparator = false; public FileInfo(VirtualFile first, EditorWindow second) { super(first, second); } diff --git a/platform/platform-impl/src/com/intellij/ide/dnd/DnDManagerImpl.java b/platform/platform-impl/src/com/intellij/ide/dnd/DnDManagerImpl.java index 2abadfe17bd9..2b07cfc16bf0 100644 --- a/platform/platform-impl/src/com/intellij/ide/dnd/DnDManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/ide/dnd/DnDManagerImpl.java @@ -736,7 +736,7 @@ public class DnDManagerImpl extends DnDManager implements Disposable { return myApp; } - public void setLastDropHandler(Component c) { + public void setLastDropHandler(@Nullable Component c) { if (c == null) { myLastDropHandler = null; } else { @@ -744,6 +744,7 @@ public class DnDManagerImpl extends DnDManager implements Disposable { } } + @Nullable public Component getLastDropHandler() { return myLastDropHandler != null ? myLastDropHandler.get() : null; } diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/InstalledPluginsTableModel.java b/platform/platform-impl/src/com/intellij/ide/plugins/InstalledPluginsTableModel.java index c651ebc863bc..1f9d4213bcfa 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/InstalledPluginsTableModel.java +++ b/platform/platform-impl/src/com/intellij/ide/plugins/InstalledPluginsTableModel.java @@ -103,8 +103,10 @@ public class InstalledPluginsTableModel extends PluginTableModel { } public static void updateExistingPlugin(IdeaPluginDescriptor descriptor, IdeaPluginDescriptor existing) { - updateExistingPluginInfo(descriptor, existing); - updatedPlugins.add(existing.getPluginId()); + if (existing != null) { + updateExistingPluginInfo(descriptor, existing); + updatedPlugins.add(existing.getPluginId()); + } } public String getPluginHostUrl(String idString) { diff --git a/platform/platform-impl/src/com/intellij/openapi/application/impl/TransferToPooledThreadQueue.java b/platform/platform-impl/src/com/intellij/openapi/application/impl/TransferToPooledThreadQueue.java index 87de511bb733..5a8a590f1069 100644 --- a/platform/platform-impl/src/com/intellij/openapi/application/impl/TransferToPooledThreadQueue.java +++ b/platform/platform-impl/src/com/intellij/openapi/application/impl/TransferToPooledThreadQueue.java @@ -15,27 +15,32 @@ */ package com.intellij.openapi.application.impl; -import com.intellij.concurrency.JobScheduler; import com.intellij.openapi.util.Condition; +import com.intellij.util.ConcurrencyUtil; import com.intellij.util.Processor; import com.intellij.util.containers.TransferToEDTQueue; +import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; +import java.util.concurrent.ScheduledThreadPoolExecutor; + /** - * Allows to process elements in the Application pooled thread. - * Processes elements in batches, no longer than 200ms per batch, and reschedules processing later for longer batches. + * Passes elements for processing in the dedicated thread. + * Processes elements in batches, no longer than {maxUnitOfWorkThresholdMs}ms per batch, and reschedules processing later for longer batches. * Usage: {@link #offer(Object)} } : schedules element for processing in a pooled thread */ public class TransferToPooledThreadQueue extends TransferToEDTQueue { - public TransferToPooledThreadQueue(@NotNull String name, - @NotNull Processor processorInEDT, + private final ScheduledThreadPoolExecutor myExecutor = ConcurrencyUtil.newSingleScheduledThreadExecutor("Encoding detection thread"); + + public TransferToPooledThreadQueue(@NonNls @NotNull String name, @NotNull Condition shutUpCondition, - int maxUnitOfWorkThresholdMs) { - super(name, processorInEDT, shutUpCondition, maxUnitOfWorkThresholdMs); + int maxUnitOfWorkThresholdMs, + @NotNull Processor processor) { + super(name, processor, shutUpCondition, maxUnitOfWorkThresholdMs); } @Override protected void schedule(@NotNull Runnable updateRunnable) { - JobScheduler.getScheduler().execute(updateRunnable); + myExecutor.execute(updateRunnable); } } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/LexerEditorHighlighter.java b/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/LexerEditorHighlighter.java index 8e04becd8b79..29dea0ca4f28 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/LexerEditorHighlighter.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/LexerEditorHighlighter.java @@ -16,6 +16,7 @@ package com.intellij.openapi.editor.ex.util; import com.intellij.lexer.Lexer; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.HighlighterColors; @@ -317,7 +318,7 @@ public class LexerEditorHighlighter implements EditorHighlighter, PrioritizedDoc } processor.finish(); - if(myEditor != null) { + if(myEditor != null && !ApplicationManager.getApplication().isHeadlessEnvironment()) { UIUtil.invokeLaterIfNeeded(new DumbAwareRunnable() { @Override public void run() { diff --git a/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectImpl.java b/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectImpl.java index b817a1c5ea74..58342c628f2d 100644 --- a/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectImpl.java @@ -331,50 +331,51 @@ public class ProjectImpl extends ComponentManagerImpl implements ProjectEx { public void save() { if (ApplicationManagerEx.getApplicationEx().isDoNotSave()) return; //no need to save - if (mySavingInProgress.compareAndSet(false, true)) { - try { - if (isToSaveProjectName()) { - final IProjectStore stateStore = getStateStore(); - final VirtualFile baseDir = stateStore.getProjectBaseDir(); - if (baseDir != null && baseDir.isValid()) { - final VirtualFile ideaDir = baseDir.findChild(DIRECTORY_STORE_FOLDER); - if (ideaDir != null && ideaDir.isValid() && ideaDir.isDirectory()) { - final File nameFile = new File(ideaDir.getPath(), ".name"); - try { - FileUtil.writeToFile(nameFile, getName().getBytes("UTF-8"), false); - myOldName = null; - } - catch (IOException e) { - LOG.info("Unable to store project name to: " + nameFile.getPath()); - } + if (!mySavingInProgress.compareAndSet(false, true)) { + return; + } + try { + if (isToSaveProjectName()) { + final IProjectStore stateStore = getStateStore(); + final VirtualFile baseDir = stateStore.getProjectBaseDir(); + if (baseDir != null && baseDir.isValid()) { + final VirtualFile ideaDir = baseDir.findChild(DIRECTORY_STORE_FOLDER); + if (ideaDir != null && ideaDir.isValid() && ideaDir.isDirectory()) { + final File nameFile = new File(ideaDir.getPath(), ".name"); + try { + FileUtil.writeToFile(nameFile, getName().getBytes("UTF-8"), false); + myOldName = null; + } + catch (IOException e) { + LOG.info("Unable to store project name to: " + nameFile.getPath()); } } } + } - StoreUtil.doSave(getStateStore()); - } - catch (IComponentStore.SaveCancelledException e) { - LOG.info(e); - } - catch (PluginException e) { - PluginManager.disablePlugin(e.getPluginId().getIdString()); - Notification notification = new Notification( - PLUGIN_SETTINGS_ERROR, - "Unable to save plugin settings!", - "

The plugin " + e.getPluginId() + " failed to save settings and has been disabled. Please restart" + - ApplicationNamesInfo.getInstance().getFullProductName() + "

" + - (ApplicationManagerEx.getApplicationEx().isInternal() ? "

" + StringUtil.getThrowableText(e) + "

" : ""), - NotificationType.ERROR); - Notifications.Bus.notify(notification, this); - LOG.info("Unable to save plugin settings",e); - } - catch (IOException e) { - MessagesEx.error(this, ProjectBundle.message("project.save.error", e.getMessage())).showLater(); - LOG.info("Error saving project", e); - } finally { - mySavingInProgress.set(false); - ApplicationManager.getApplication().getMessageBus().syncPublisher(ProjectSaved.TOPIC).saved(this); - } + StoreUtil.doSave(getStateStore()); + } + catch (IComponentStore.SaveCancelledException e) { + LOG.info(e); + } + catch (PluginException e) { + PluginManager.disablePlugin(e.getPluginId().getIdString()); + Notification notification = new Notification( + PLUGIN_SETTINGS_ERROR, + "Unable to save plugin settings!", + "

The plugin " + e.getPluginId() + " failed to save settings and has been disabled. Please restart" + + ApplicationNamesInfo.getInstance().getFullProductName() + "

" + + (ApplicationManagerEx.getApplicationEx().isInternal() ? "

" + StringUtil.getThrowableText(e) + "

" : ""), + NotificationType.ERROR); + Notifications.Bus.notify(notification, this); + LOG.info("Unable to save plugin settings",e); + } + catch (IOException e) { + MessagesEx.error(this, ProjectBundle.message("project.save.error", e.getMessage())).showLater(); + LOG.info("Error saving project", e); + } finally { + mySavingInProgress.set(false); + ApplicationManager.getApplication().getMessageBus().syncPublisher(ProjectSaved.TOPIC).saved(this); } } diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/encoding/EncodingManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/encoding/EncodingManagerImpl.java index 5b7d4cbfadf3..c8f9d5f94ddf 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/encoding/EncodingManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/encoding/EncodingManagerImpl.java @@ -86,15 +86,18 @@ public class EncodingManagerImpl extends EncodingManager implements PersistentSt private static final Key CACHED_CHARSET_FROM_CONTENT = Key.create("CACHED_CHARSET_FROM_CONTENT"); private final TransferToPooledThreadQueue> myChangedDocuments = new TransferToPooledThreadQueue>( - "Loading documents", new Processor>() { - @Override - public boolean process(Reference ref) { - Document document = ref.get(); - if (document == null) return true; // document gced, don't bother - handleDocument(document); - return true; - } - }, ApplicationManager.getApplication().getDisposed(), -1); // drain the whole queue, do not reschedule + "Loading documents", + ApplicationManager.getApplication().getDisposed(), + -1, // drain the whole queue, do not reschedule + new Processor>() { + @Override + public boolean process(Reference ref) { + Document document = ref.get(); + if (document == null) return true; // document gced, don't bother + handleDocument(document); + return true; + } + }); public EncodingManagerImpl(EditorFactory editorFactory) { editorFactory.getEventMulticaster().addDocumentListener(new DocumentAdapter() { diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/jar/JarFileSystemImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/jar/JarFileSystemImpl.java index ab98512cc9e4..ee58d65f44ba 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/jar/JarFileSystemImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/jar/JarFileSystemImpl.java @@ -20,6 +20,7 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.application.PathManager; import com.intellij.openapi.components.ApplicationComponent; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.io.FileAttributes; import com.intellij.openapi.util.io.FileUtil; @@ -46,6 +47,8 @@ public class JarFileSystemImpl extends JarFileSystem implements ApplicationCompo new ConcurrentHashSet() : new ConcurrentHashSet(CaseInsensitiveStringHashingStrategy.INSTANCE); + private final static Logger LOG = Logger.getInstance(JarFileSystemImpl.class); + @NonNls private static final String IDEA_JARS_NOCOPY = "idea.jars.nocopy"; private File myNoCopyJarDir; @@ -86,6 +89,7 @@ public class JarFileSystemImpl extends JarFileSystem implements ApplicationCompo SystemInfo.isFileSystemCaseSensitive)) { VirtualFile jarRootToRefresh = markDirty(jarPath); if (jarRootToRefresh != null) { + LOG.info(jarPath + " will be refreshed due to " + event); rootsToRefresh.add(jarRootToRefresh); } } diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/LocalFileSystemImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/LocalFileSystemImpl.java index 2e528457a167..40b0f50325ab 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/LocalFileSystemImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/LocalFileSystemImpl.java @@ -15,12 +15,11 @@ */ package com.intellij.openapi.vfs.impl.local; +import com.intellij.openapi.application.AccessToken; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.components.ApplicationComponent; import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.io.FileAttributes; import com.intellij.openapi.util.io.FileSystemUtil; import com.intellij.openapi.util.io.FileUtil; @@ -114,6 +113,12 @@ public final class LocalFileSystemImpl extends LocalFileSystemBase implements Ap public void disposeComponent() { } + @Override + @NotNull + public String getComponentName() { + return "LocalFileSystem"; + } + @TestOnly public void cleanupForNextTest(Set survivors) throws IOException { ApplicationManager.getApplication().runWriteAction(new Runnable() { @@ -231,31 +236,29 @@ public final class LocalFileSystemImpl extends LocalFileSystemBase implements Ap private void setUpFileWatcher() { final Application application = ApplicationManager.getApplication(); + if (application.isDisposeInProgress() || !myWatcher.isOperational()) return; - if (application.isDisposeInProgress()) return; + final AccessToken token = application.acquireReadActionLock(); + try { + synchronized (myLock) { + final WatchRequestImpl[] watchRequests = normalizeRootsForRefresh(); + final List myRecursiveRoots = new ArrayList(); + final List myFlatRoots = new ArrayList(); - if (myWatcher.isOperational()) { - application.runReadAction(new Runnable() { - @Override - public void run() { - synchronized (myLock) { - final WatchRequestImpl[] watchRequests = normalizeRootsForRefresh(); - List myRecursiveRoots = new ArrayList(); - List myFlatRoots = new ArrayList(); - - for (WatchRequestImpl root : watchRequests) { - if (root.isToWatchRecursively()) { - myRecursiveRoots.add(root.myFSRootPath); - } - else { - myFlatRoots.add(root.myFSRootPath); - } - } - - myWatcher.setWatchRoots(myRecursiveRoots, myFlatRoots); + for (WatchRequestImpl watchRequest : watchRequests) { + if (watchRequest.isToWatchRecursively()) { + myRecursiveRoots.add(watchRequest.myFSRootPath); + } + else { + myFlatRoots.add(watchRequest.myFSRootPath); } } - }); + + myWatcher.setWatchRoots(myRecursiveRoots, myFlatRoots); + } + } + finally { + token.finish(); } } @@ -280,48 +283,6 @@ public final class LocalFileSystemImpl extends LocalFileSystemBase implements Ap } } - @Override - @NotNull - public String getComponentName() { - return "LocalFileSystem"; - } - - @Override - public WatchRequest addRootToWatch(@NotNull final String rootPath, final boolean toWatchRecursively) { - if (rootPath.length() == 0 || !myWatcher.isOperational()) return null; - - Application app = ApplicationManager.getApplication(); - return app.runReadAction(new Computable() { - @Override - public WatchRequest compute() { - synchronized (myLock) { - final WatchRequestImpl result = new WatchRequestImpl(rootPath, toWatchRecursively); - boolean alreadyWatched = isAlreadyWatched(result); - if (!alreadyWatched) { - final VirtualFile existingFile = findFileByPathIfCached(rootPath); - if (existingFile != null) { - final ModalityState modalityState = ModalityState.defaultModalityState(); - RefreshQueue.getInstance().refresh(true, toWatchRecursively, null, modalityState, existingFile); - if (existingFile.isDirectory() && !toWatchRecursively && existingFile instanceof NewVirtualFile) { - for (VirtualFile child : ((NewVirtualFile)existingFile).getCachedChildren()) { - RefreshQueue.getInstance().refresh(true, false, null, modalityState, child); - } - } - } - } - myRootsToWatch.add(result); - if (alreadyWatched) { - result.myDominated = true; - return result; - } - myCachedNormalizedRequests = null; - setUpFileWatcher(); - return result; - } - } - }); - } - private boolean isAlreadyWatched(final WatchRequestImpl request) { for (final WatchRequestImpl current : normalizeRootsForRefresh()) { if (dominates(current, request)) return true; @@ -339,74 +300,114 @@ public final class LocalFileSystemImpl extends LocalFileSystemBase implements Ap @Override @NotNull - public Set addRootsToWatch(@NotNull final Collection rootPaths, final boolean toWatchRecursively) { - if (!myWatcher.isOperational()) return Collections.emptySet(); + public Set addRootsToWatch(@NotNull final Collection rootPaths, final boolean watchRecursively) { + if (rootPaths.isEmpty() || !myWatcher.isOperational()) { + return Collections.emptySet(); + } + else { + return replaceWatchedRoots(Collections.emptySet(), rootPaths, watchRecursively); + } + } - final Set result = new HashSet(); - final Set filesToSynchronize = new HashSet(); + @Override + public void removeWatchedRoots(@NotNull final Collection watchRequests) { + if (watchRequests.isEmpty()) return; - Application application = ApplicationManager.getApplication(); - application.runReadAction(new Runnable() { - public void run() { - synchronized (myLock) { - for (String rootPath : rootPaths) { - LOG.assertTrue(rootPath != null); - if (rootPath.length() > 0) { - final WatchRequestImpl request = new WatchRequestImpl(rootPath, toWatchRecursively); - final VirtualFile existingFile = findFileByPathIfCached(rootPath); - if (existingFile != null) { - if (!isAlreadyWatched(request)) { - filesToSynchronize.add(existingFile); - } - } - result.add(request); - myRootsToWatch.add(request); //add in any case, safe to add inplace without copying myRootsToWatch before the loop - } - } + final AccessToken token = ApplicationManager.getApplication().acquireReadActionLock(); + try { + synchronized (myLock) { + final boolean update = doRemoveWatchedRoots(watchRequests); + if (update) { myCachedNormalizedRequests = null; setUpFileWatcher(); } } - }); + } + finally { + token.finish(); + } + } - if (!application.isUnitTestMode() && !filesToSynchronize.isEmpty()) { - for (VirtualFile file : filesToSynchronize) { - if (file instanceof NewVirtualFile && file.getFileSystem() instanceof LocalFileSystem) { - ((NewVirtualFile)file).markDirtyRecursively(); + @Override + public Set replaceWatchedRoots(@NotNull final Collection watchRequests, + @NotNull final Collection rootPaths, + final boolean watchRecursively) { + if (rootPaths.isEmpty() || !myWatcher.isOperational()) { + removeWatchedRoots(watchRequests); + return Collections.emptySet(); + } + + final Set result = new HashSet(); + final Set filesToSync = new HashSet(); + + final AccessToken token = ApplicationManager.getApplication().acquireReadActionLock(); + try { + synchronized (myLock) { + final boolean update = doAddRootsToWatch(rootPaths, watchRecursively, result, filesToSync) || + doRemoveWatchedRoots(watchRequests); + if (update) { + myCachedNormalizedRequests = null; + setUpFileWatcher(); } } - refreshFiles(filesToSynchronize, true, toWatchRecursively, null); } + finally { + token.finish(); + } + + syncFiles(filesToSync, watchRecursively); return result; } - @Override - public void removeWatchedRoot(@NotNull final WatchRequest watchRequest) { - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - synchronized (myLock) { - if (myRootsToWatch.remove((WatchRequestImpl)watchRequest) && !((WatchRequestImpl)watchRequest).myDominated) { - myCachedNormalizedRequests = null; - setUpFileWatcher(); + private boolean doAddRootsToWatch(@NotNull final Collection roots, + final boolean recursively, + @NotNull final Set results, + @NotNull final Set filesToSync) { + boolean update = false; + + for (String root : roots) { + final WatchRequestImpl result = new WatchRequestImpl(root, recursively); + final boolean alreadyWatched = isAlreadyWatched(result); + + if (!alreadyWatched) { + final VirtualFile existingFile = findFileByPathIfCached(root); + if (existingFile != null) { + if (existingFile.isDirectory() && !recursively && existingFile instanceof NewVirtualFile) { + filesToSync.addAll(((NewVirtualFile)existingFile).getCachedChildren()); } } } - }); + result.myDominated = alreadyWatched; + myRootsToWatch.add(result); + results.add(result); + + update |= !alreadyWatched; + } + + return update; } - @Override - public void removeWatchedRoots(@NotNull final Collection rootsToWatch) { - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - synchronized (myLock) { - if (myRootsToWatch.removeAll(rootsToWatch)) { - myCachedNormalizedRequests = null; - setUpFileWatcher(); - } - } + private void syncFiles(@NotNull final Set filesToSync, final boolean watchRecursively) { + if (filesToSync.isEmpty() || ApplicationManager.getApplication().isUnitTestMode()) return; + + for (VirtualFile file : filesToSync) { + if (file instanceof NewVirtualFile && file.getFileSystem() instanceof LocalFileSystem) { + ((NewVirtualFile)file).markDirtyRecursively(); } - }); + } + refreshFiles(filesToSync, true, watchRecursively, null); + } + + private boolean doRemoveWatchedRoots(@NotNull final Collection watchRequests) { + boolean update = false; + + for (WatchRequest watchRequest : watchRequests) { + final boolean wasWatched = myRootsToWatch.remove((WatchRequestImpl)watchRequest) && !((WatchRequestImpl)watchRequest).myDominated; + update |= wasWatched; + } + + return update; } @Override diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/win32/Win32LocalFileSystem.java b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/win32/Win32LocalFileSystem.java index 621f7612e4ef..0bba1c9e3913 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/win32/Win32LocalFileSystem.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/win32/Win32LocalFileSystem.java @@ -181,24 +181,19 @@ public class Win32LocalFileSystem extends LocalFileSystemBase { } } - @Override - public WatchRequest addRootToWatch(@NotNull String rootPath, boolean toWatchRecursively) { - throw new UnsupportedOperationException(); - } - @NotNull @Override - public Set addRootsToWatch(@NotNull Collection rootPaths, boolean toWatchRecursively) { + public Set addRootsToWatch(@NotNull Collection rootPaths, boolean watchRecursively) { throw new UnsupportedOperationException(); } @Override - public void removeWatchedRoots(@NotNull Collection rootsToWatch) { + public void removeWatchedRoots(@NotNull Collection watchRequests) { throw new UnsupportedOperationException(); } @Override - public void removeWatchedRoot(@NotNull WatchRequest watchRequest) { + public Set replaceWatchedRoots(@NotNull Collection watchRequests, @NotNull Collection rootPaths, boolean watchRecursively) { throw new UnsupportedOperationException(); } diff --git a/platform/platform-resources-en/src/messages/ActionsBundle.properties b/platform/platform-resources-en/src/messages/ActionsBundle.properties index 445b1a28b709..4a8d678f245b 100644 --- a/platform/platform-resources-en/src/messages/ActionsBundle.properties +++ b/platform/platform-resources-en/src/messages/ActionsBundle.properties @@ -375,10 +375,10 @@ action.ExpandAllRegions.text=_Expand All action.ExpandAllRegions.description=Expand all folding regions action.CollapseAllRegions.text=Collapse _All action.CollapseAllRegions.description=Collapse all folding regions -action.ExpandJavadocs.text=Expand Java_docs -action.ExpandJavadocs.description=Expand all javadoc comments -action.CollapseJavadocs.text=Collapse _Javadocs -action.CollapseJavadocs.description=Collapse all javadoc comments +action.ExpandDocComments.text=Expand _doc comments +action.ExpandDocComments.description=Expand all documentation comments +action.CollapseDocComments.text=Collapse d_oc comments +action.CollapseDocComments.description=Collapse all documentation comments action.CollapseSelection.text=Fold _Selection / Remove region action.CollapseSelection.description=Fold selected block action.CollapseBlock.text=Fold Code _Block diff --git a/platform/platform-resources-en/src/messages/ExecutionBundle.properties b/platform/platform-resources-en/src/messages/ExecutionBundle.properties index bb3a5443a359..425bf9a1b642 100644 --- a/platform/platform-resources-en/src/messages/ExecutionBundle.properties +++ b/platform/platform-resources-en/src/messages/ExecutionBundle.properties @@ -282,6 +282,10 @@ run.configuration.cannot.find.vm.executable=Cannot find VM executable logs.tab.title=Logs before.launch.panel.title=Before Launch +before.launch.panel.cyclic_dependency_warning=''{0}'' has already configured to be launched before {1}.\nSuch cyclic dependencies are not allowed. +before.launch.run.another.configuration=Run another Configuration +before.launch.run.certain.configuration=Run ''{0}'' +before.launch.run.unknown.task=Unknown task action.name.save.as.configuration=Save As default.junit.config.name.all.in.module=All in {0} default.junit.config.name.all.in.package.in.module={0} in {1} diff --git a/platform/platform-resources-en/src/misc/registry.properties b/platform/platform-resources-en/src/misc/registry.properties index ae842e42edca..00a592aaff6a 100644 --- a/platform/platform-resources-en/src/misc/registry.properties +++ b/platform/platform-resources-en/src/misc/registry.properties @@ -151,9 +151,6 @@ compiler.server.use.external.javac.process.description=Run javac compiler in ext compiler.server.ping.interval=5 compiler.server.ping.interval.description=Interval in seconds between ping requests the IDE periodically sends to server. If server does not receive pings for some time, it shuts down. Specify -1 to disable this feature. -compiler.server.in.process=false -compiler.server.in.process.description=Launch compiler server in IDEA process - compiler.server.debug.port=-1 #compiler.server.javac.debug.port=-1 diff --git a/platform/platform-resources/src/idea/LangActions.xml b/platform/platform-resources/src/idea/LangActions.xml index 4aad1912dcf2..ca0c5dcbf463 100644 --- a/platform/platform-resources/src/idea/LangActions.xml +++ b/platform/platform-resources/src/idea/LangActions.xml @@ -217,7 +217,10 @@ - + + + + diff --git a/platform/testFramework/src/com/intellij/mock/MockLocalFileSystem.java b/platform/testFramework/src/com/intellij/mock/MockLocalFileSystem.java index 8c96630f68b2..ed6438d539cc 100644 --- a/platform/testFramework/src/com/intellij/mock/MockLocalFileSystem.java +++ b/platform/testFramework/src/com/intellij/mock/MockLocalFileSystem.java @@ -81,24 +81,20 @@ public class MockLocalFileSystem extends LocalFileSystem { public void refreshFiles(@NotNull Iterable files, boolean async, boolean recursive, @Nullable Runnable onFinish) { } - @Override - @Nullable - public WatchRequest addRootToWatch(@NotNull final String rootPath, final boolean toWatchRecursively) { - throw new UnsupportedOperationException("'addRootToWatch' not implemented in " + getClass().getName()); - } - @Override @NotNull - public Set addRootsToWatch(@NotNull final Collection rootPaths, final boolean toWatchRecursively) { - throw new UnsupportedOperationException("'addRootsToWatch' not implemented in " + getClass().getName()); + public Set addRootsToWatch(@NotNull final Collection rootPaths, final boolean watchRecursively) { + throw new UnsupportedOperationException("Not implemented in " + getClass().getName()); } @Override public void removeWatchedRoots(@NotNull final Collection rootsToWatch) { + throw new UnsupportedOperationException("Not implemented in " + getClass().getName()); } @Override - public void removeWatchedRoot(@NotNull final WatchRequest watchRequest) { + public Set replaceWatchedRoots(@NotNull Collection watchRequests, @NotNull Collection rootPaths, boolean watchRecursively) { + throw new UnsupportedOperationException("Not implemented in " + getClass().getName()); } @Override diff --git a/platform/testFramework/src/com/intellij/mock/MockRunManager.java b/platform/testFramework/src/com/intellij/mock/MockRunManager.java index cb751f32d734..26cfb96470ab 100644 --- a/platform/testFramework/src/com/intellij/mock/MockRunManager.java +++ b/platform/testFramework/src/com/intellij/mock/MockRunManager.java @@ -10,7 +10,7 @@ import org.jetbrains.annotations.NotNull; import javax.swing.*; import java.util.Collection; import java.util.Collections; -import java.util.Map; +import java.util.List; /** * @author gregsh @@ -100,9 +100,7 @@ public class MockRunManager extends RunManagerEx { } @Override - public void addConfiguration(RunnerAndConfigurationSettings settings, - boolean isShared, - Map, BeforeRunTask> tasks) { + public void addConfiguration(RunnerAndConfigurationSettings settings, boolean isShared, List tasks) { } @Override @@ -116,19 +114,26 @@ public class MockRunManager extends RunManagerEx { @NotNull @Override - public Map, BeforeRunTask> getBeforeRunTasks(RunConfiguration settings) { - return Collections.emptyMap(); - } - - @Override - public T getBeforeRunTask(RunConfiguration settings, Key taskProviderID) { + public List getBeforeRunTasks(RunConfiguration settings) { return null; } @NotNull @Override - public Collection getBeforeRunTasks(Key taskProviderID, boolean includeOnlyActiveTasks) { - return null; + public List getBeforeRunTasks(RunConfiguration settings, boolean includeOnlyActiveTasks) { + return Collections.emptyList(); + } + + @NotNull + @Override + public List getBeforeRunTasks(Key taskProviderID, boolean includeOnlyActiveTasks) { + return Collections.emptyList(); + } + + @NotNull + @Override + public List getBeforeRunTasks(RunConfiguration settings, Key taskProviderID) { + return Collections.emptyList(); } @Override diff --git a/platform/testFramework/src/com/intellij/testFramework/PsiTestUtil.java b/platform/testFramework/src/com/intellij/testFramework/PsiTestUtil.java index bb23a883e389..78f95bd52d1a 100644 --- a/platform/testFramework/src/com/intellij/testFramework/PsiTestUtil.java +++ b/platform/testFramework/src/com/intellij/testFramework/PsiTestUtil.java @@ -286,7 +286,7 @@ import java.util.Collection; ApplicationManager.getApplication().runWriteAction(new Runnable() { public void run() { final ModifiableRootModel model = ModuleRootManager.getInstance(module).getModifiableModel(); - final String parentUrl = VirtualFileManager.constructUrl(JarFileSystem.PROTOCOL, libDir); + final String parentUrl = VirtualFileManager.constructUrl(classRoots[0].endsWith(".jar!/") ? JarFileSystem.PROTOCOL : LocalFileSystem.PROTOCOL, libDir); final Library library = model.getModuleLibraryTable().createLibrary(libName); final Library.ModifiableModel libModifiableModel = library.getModifiableModel(); for (String classRoot : classRoots) { diff --git a/platform/testFramework/src/com/intellij/testFramework/vcs/MockChangeList.java b/platform/testFramework/src/com/intellij/testFramework/vcs/MockChangeList.java new file mode 100644 index 000000000000..f1242a36fa58 --- /dev/null +++ b/platform/testFramework/src/com/intellij/testFramework/vcs/MockChangeList.java @@ -0,0 +1,86 @@ +/* + * Copyright 2000-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.testFramework.vcs; + +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.LocalChangeList; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * @author Kirill Likhodedov + */ +public class MockChangeList extends LocalChangeList { + + Collection myChanges = new ArrayList(); + private final String myName; + + public MockChangeList(String name) { + myName = name; + } + + public void add(Change change) { + myChanges.add(change); + } + + @Override + public Collection getChanges() { + return myChanges; + } + + @NotNull + @Override + public String getName() { + return myName; + } + + @Override + public void setName(@NotNull String name) { + throw new UnsupportedOperationException(); + } + + @Override + public String getComment() { + throw new UnsupportedOperationException(); + } + + @Override + public void setComment(String comment) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isDefault() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isReadOnly() { + throw new UnsupportedOperationException(); + } + + @Override + public void setReadOnly(boolean isReadOnly) { + throw new UnsupportedOperationException(); + } + + @Override + public LocalChangeList copy() { + throw new UnsupportedOperationException(); + } +} diff --git a/platform/testFramework/testFramework.iml b/platform/testFramework/testFramework.iml index a234c1da1384..2a50c47971ee 100644 --- a/platform/testFramework/testFramework.iml +++ b/platform/testFramework/testFramework.iml @@ -20,6 +20,7 @@ + diff --git a/platform/testFramework/testSrc/com/intellij/testFramework/vcs/MockChangeListManager.java b/platform/testFramework/testSrc/com/intellij/testFramework/vcs/MockChangeListManager.java new file mode 100644 index 000000000000..fd299b982115 --- /dev/null +++ b/platform/testFramework/testSrc/com/intellij/testFramework/vcs/MockChangeListManager.java @@ -0,0 +1,321 @@ +package com.intellij.testFramework.vcs; + +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.FileStatus; +import com.intellij.openapi.vcs.changes.*; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.Consumer; +import com.intellij.util.continuation.ContinuationPause; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.util.*; + +/** + * @author Kirill Likhodedov + */ +public class MockChangeListManager extends ChangeListManager { + + public static final String DEFAULT_CHANGE_LIST_NAME = "Default"; + + private final Map myChangeLists = new HashMap(); + private LocalChangeList myActiveChangeList; + private final MockChangeList myDefaultChangeList; + + public MockChangeListManager() { + myDefaultChangeList = new MockChangeList(DEFAULT_CHANGE_LIST_NAME); + myChangeLists.put(DEFAULT_CHANGE_LIST_NAME, myDefaultChangeList); + myActiveChangeList = myDefaultChangeList; + } + + public void addChanges(Change... changes) { + MockChangeList changeList = myChangeLists.get(DEFAULT_CHANGE_LIST_NAME); + for (Change change : changes) { + changeList.add(change); + } + } + + @Override + public void scheduleUpdate() { + throw new UnsupportedOperationException(); + } + + @Override + public void scheduleUpdate(boolean updateUnversionedFiles) { + throw new UnsupportedOperationException(); + } + + @Override + public void invokeAfterUpdate(Runnable afterUpdate, + InvokeAfterUpdateMode mode, + String title, + ModalityState state) { + throw new UnsupportedOperationException(); + } + + @Override + public void invokeAfterUpdate(Runnable afterUpdate, + InvokeAfterUpdateMode mode, + String title, + Consumer dirtyScopeManager, + ModalityState state) { + afterUpdate.run(); + } + + @Override + public boolean ensureUpToDate(boolean canBeCanceled) { + throw new UnsupportedOperationException(); + } + + @Override + public List getChangeListsCopy() { + return new ArrayList(myChangeLists.values()); + } + + @NotNull + @Override + public List getChangeLists() { + return getChangeListsCopy(); + } + + @Override + public List getAffectedPaths() { + throw new UnsupportedOperationException(); + } + + @NotNull + @Override + public List getAffectedFiles() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isFileAffected(VirtualFile file) { + throw new UnsupportedOperationException(); + } + + @NotNull + @Override + public Collection getAllChanges() { + Collection changes = new ArrayList(); + for (MockChangeList list : myChangeLists.values()) { + changes.addAll(list.getChanges()); + } + return changes; + } + + @Override + public LocalChangeList findChangeList(String name) { + throw new UnsupportedOperationException(); + } + + @Override + public LocalChangeList getChangeList(String id) { + throw new UnsupportedOperationException(); + } + + @Override + public LocalChangeList getDefaultChangeList() { + return myActiveChangeList; + } + + @Override + public boolean isDefaultChangeList(ChangeList list) { + throw new UnsupportedOperationException(); + } + + @Override + public LocalChangeList getChangeList(Change change) { + throw new UnsupportedOperationException(); + } + + @Override + public String getChangeListNameIfOnlyOne(Change[] changes) { + throw new UnsupportedOperationException(); + } + + @NotNull + @Override + public Runnable prepareForChangeDeletion(Collection changes) { + throw new UnsupportedOperationException(); + } + + @Override + public Change getChange(@NotNull VirtualFile file) { + throw new UnsupportedOperationException(); + } + + @Override + public LocalChangeList getChangeList(@NotNull VirtualFile file) { + throw new UnsupportedOperationException(); + } + + @Override + public Change getChange(FilePath file) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isUnversioned(VirtualFile file) { + throw new UnsupportedOperationException(); + } + + @NotNull + @Override + public FileStatus getStatus(VirtualFile file) { + throw new UnsupportedOperationException(); + } + + @NotNull + @Override + public Collection getChangesIn(VirtualFile dir) { + throw new UnsupportedOperationException(); + } + + @NotNull + @Override + public Collection getChangesIn(FilePath path) { + throw new UnsupportedOperationException(); + } + + @Override + public void addChangeListListener(ChangeListListener listener) { + throw new UnsupportedOperationException(); + } + + @Override + public void removeChangeListListener(ChangeListListener listener) { + throw new UnsupportedOperationException(); + } + + @Override + public void registerCommitExecutor(CommitExecutor executor) { + throw new UnsupportedOperationException(); + } + + @Override + public void commitChanges(LocalChangeList changeList, List changes) { + throw new UnsupportedOperationException(); + } + + @Override + public void commitChangesSynchronously(LocalChangeList changeList, List changes) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean commitChangesSynchronouslyWithResult(LocalChangeList changeList, List changes) { + throw new UnsupportedOperationException(); + } + + @Override + public void reopenFiles(List paths) { + throw new UnsupportedOperationException(); + } + + @Override + public List getRegisteredExecutors() { + throw new UnsupportedOperationException(); + } + + @Override + public void addFilesToIgnore(IgnoredFileBean... ignoredFiles) { + throw new UnsupportedOperationException(); + } + + @Override + public void setFilesToIgnore(IgnoredFileBean... ignoredFiles) { + throw new UnsupportedOperationException(); + } + + @Override + public IgnoredFileBean[] getFilesToIgnore() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isIgnoredFile(@NotNull VirtualFile file) { + throw new UnsupportedOperationException(); + } + + @Override + public String getSwitchedBranch(VirtualFile file) { + throw new UnsupportedOperationException(); + } + + @Override + public String getDefaultListName() { + throw new UnsupportedOperationException(); + } + + @Override + public void freeze(ContinuationPause context, String reason) { + throw new UnsupportedOperationException(); + } + + @Override + public void letGo() { + throw new UnsupportedOperationException(); + } + + @Override + public String isFreezed() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isFreezedWithNotification(@Nullable String modalTitle) { + throw new UnsupportedOperationException(); + } + + @Override + public List getModifiedWithoutEditing() { + throw new UnsupportedOperationException("Not implemented"); + } + + @Override + public LocalChangeList addChangeList(@NotNull String name, @Nullable String comment) { + MockChangeList changeList = new MockChangeList(name); + myChangeLists.put(name, changeList); + return changeList; + } + + @Override + public void setDefaultChangeList(@NotNull LocalChangeList list) { + myActiveChangeList = list; + } + + @Override + public void removeChangeList(String name) { + throw new UnsupportedOperationException(); + } + + @Override + public void removeChangeList(LocalChangeList list) { + myChangeLists.remove(list.getName()); + if (myActiveChangeList.equals(list)) { + myActiveChangeList = myDefaultChangeList; + } + } + + @Override + public void moveChangesTo(LocalChangeList list, Change[] changes) { + } + + @Override + public boolean setReadOnly(String name, boolean value) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean editName(@NotNull String fromName, @NotNull String toName) { + throw new UnsupportedOperationException(); + } + + @Override + public String editComment(@NotNull String fromName, String newComment) { + throw new UnsupportedOperationException(); + } +} diff --git a/platform/testFramework/testSrc/com/intellij/testFramework/vcs/MockContentRevision.java b/platform/testFramework/testSrc/com/intellij/testFramework/vcs/MockContentRevision.java new file mode 100644 index 000000000000..ca558aa04891 --- /dev/null +++ b/platform/testFramework/testSrc/com/intellij/testFramework/vcs/MockContentRevision.java @@ -0,0 +1,45 @@ + +package com.intellij.testFramework.vcs; + +import com.intellij.openapi.vcs.changes.ContentRevision; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.history.VcsRevisionNumber; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; + +/** + * @author yole + */ +public class MockContentRevision implements ContentRevision { + private final FilePath myPath; + private final VcsRevisionNumber myRevisionNumber; + + public MockContentRevision(final FilePath path, final VcsRevisionNumber revisionNumber) { + myPath = path; + myRevisionNumber = revisionNumber; + } + + @Override + @Nullable + public String getContent() throws VcsException { + return null; + } + + @Override + @NotNull + public FilePath getFile() { + return myPath; + } + + @Override + @NotNull + public VcsRevisionNumber getRevisionNumber() { + return myRevisionNumber; + } + + @Override + public String toString() { + return myPath.getName() + ":" + myRevisionNumber; + } +} diff --git a/platform/util/src/com/intellij/openapi/util/text/StringUtil.java b/platform/util/src/com/intellij/openapi/util/text/StringUtil.java index 5129886f6768..5e50054526fa 100644 --- a/platform/util/src/com/intellij/openapi/util/text/StringUtil.java +++ b/platform/util/src/com/intellij/openapi/util/text/StringUtil.java @@ -44,6 +44,7 @@ public class StringUtil extends StringUtilRt { @NonNls private static final Pattern EOL_SPLIT_PATTERN = Pattern.compile(" *(\r|\n|\r\n)+ *"); public static final NotNullFunction QUOTER = new NotNullFunction() { + @Override @NotNull public String fun(String s) { return "\"" + s + "\""; @@ -51,18 +52,20 @@ public class StringUtil extends StringUtilRt { }; public static final NotNullFunction SINGLE_QUOTER = new NotNullFunction() { + @Override @NotNull public String fun(String s) { return "'" + s + "'"; } }; - public static List getWordsInStringLongestFirst(String find) { + @NotNull + public static List getWordsInStringLongestFirst(@NotNull String find) { List words = getWordsIn(find); // hope long words are rare Collections.sort(words, new Comparator() { @Override - public int compare(final String o1, final String o2) { + public int compare(@NotNull final String o1, @NotNull final String o2) { return o2.length() - o1.length(); } }); @@ -70,31 +73,34 @@ public class StringUtil extends StringUtilRt { } @NotNull - public static String escapePattern(final @NotNull String text) { + public static String escapePattern(@NotNull final String text) { return replace(replace(text, "'", "''"), "{", "'{'"); } + @NotNull public static Function createToStringFunction(Class cls) { return new Function() { @Override - public String fun(T o) { + public String fun(@NotNull T o) { return o.toString(); } }; } - public static Function TRIMMER = new Function() { + @NotNull public static Function TRIMMER = new Function() { @Nullable @Override - public String fun(String s) { + public String fun(@Nullable String s) { return s == null ? null : s.trim(); } }; + @Nullable public static String replace(@NonNls @NotNull String text, @NonNls @NotNull String oldS, @NonNls @Nullable String newS) { return replace(text, oldS, newS, false); } + @Nullable public static String replaceIgnoreCase(@NotNull String text, @NotNull String oldS, @Nullable String newS) { return replace(text, oldS, newS, true); } @@ -128,6 +134,7 @@ public class StringUtil extends StringUtilRt { return newBuffer == null ? buffer : newBuffer.toString(); } + @Nullable public static String replace(@NotNull final String text, @NotNull final String oldS, @Nullable final String newS, boolean ignoreCase) { if (text.length() < oldS.length()) return text; @@ -222,6 +229,7 @@ public class StringUtil extends StringUtilRt { return html.replaceAll("<(.|\n)*?>", ""); } + @Nullable public static String toLowerCase(@Nullable final String str) { //noinspection ConstantConditions return str == null ? null : str.toLowerCase(); @@ -411,11 +419,12 @@ public class StringUtil extends StringUtilRt { return false; } + @NotNull public static NotNullFunction escaper(final boolean escapeSlash, @Nullable final String additionalChars) { return new NotNullFunction() { @NotNull @Override - public String fun(String dom) { + public String fun(@NotNull String dom) { final StringBuilder builder = StringBuilderSpinAllocator.alloc(); try { StringUtil.escapeStringCharacters(dom.length(), dom, additionalChars, escapeSlash, builder); @@ -523,7 +532,8 @@ public class StringUtil extends StringUtilRt { /** * This is just an optimized version of Matcher.quoteReplacement */ - public static String quoteReplacement(String s) { + @NotNull + public static String quoteReplacement(@NotNull String s) { boolean needReplacements = false; for (int i = 0; i < s.length(); i++) { @@ -690,7 +700,7 @@ public class StringUtil extends StringUtilRt { @NotNull public static String capitalize(@NotNull String s) { - if (s.length() == 0) return s; + if (s.isEmpty()) return s; if (s.length() == 1) return StringUtilRt.toUpperCase(s); // Optimization @@ -699,7 +709,7 @@ public class StringUtil extends StringUtilRt { } public static boolean isCapitalized(@Nullable String s) { - return s != null && s.length() > 0 && Character.isUpperCase(s.charAt(0)); + return s != null && !s.isEmpty() && Character.isUpperCase(s.charAt(0)); } @NotNull @@ -710,7 +720,7 @@ public class StringUtil extends StringUtilRt { return capitalize(s); } - public static int stringHashCode(CharSequence chars) { + public static int stringHashCode(@NotNull CharSequence chars) { if (chars instanceof String) return chars.hashCode(); if (chars instanceof CharSequenceWithStringHash) return chars.hashCode(); if (chars instanceof CharArrayCharSequence) return chars.hashCode(); @@ -718,7 +728,7 @@ public class StringUtil extends StringUtilRt { return stringHashCode(chars, 0, chars.length()); } - public static int stringHashCode(CharSequence chars, int from, int to) { + public static int stringHashCode(@NotNull CharSequence chars, int from, int to) { int h = 0; for (int off = from; off < to; off++) { h = 31 * h + chars.charAt(off); @@ -734,7 +744,7 @@ public class StringUtil extends StringUtilRt { return h; } - public static int stringHashCodeInsensitive(char[] chars, int from, int to) { + public static int stringHashCodeInsensitive(@NotNull char[] chars, int from, int to) { int h = 0; for (int off = from; off < to; off++) { h = 31 * h + toLowerCase(chars[off]); @@ -742,7 +752,7 @@ public class StringUtil extends StringUtilRt { return h; } - public static int stringHashCodeInsensitive(CharSequence chars, int from, int to) { + public static int stringHashCodeInsensitive(@NotNull CharSequence chars, int from, int to) { int h = 0; for (int off = from; off < to; off++) { h = 31 * h + toLowerCase(chars.charAt(off)); @@ -762,7 +772,7 @@ public class StringUtil extends StringUtilRt { * @param secondPrefix * @return */ - public static boolean startsWithConcatenationOf(String testee, String firstPrefix, String secondPrefix) { + public static boolean startsWithConcatenationOf(@NotNull String testee, @NotNull String firstPrefix, @NotNull String secondPrefix) { int l1 = firstPrefix.length(); int l2 = secondPrefix.length(); if (testee.length() < l1 + l2) return false; @@ -777,7 +787,8 @@ public class StringUtil extends StringUtilRt { return s; } - public static String trimLog(final String text, final int limit) { + @NotNull + public static String trimLog(@NotNull final String text, final int limit) { if (limit > 5 && text.length() > limit) { return text.substring(0, limit - 5) + " ...\n"; } @@ -833,11 +844,11 @@ public class StringUtil extends StringUtilRt { } public static boolean isNotEmpty(@Nullable String s) { - return s != null && s.length() > 0; + return s != null && !s.isEmpty(); } public static boolean isEmpty(@Nullable String s) { - return s == null || s.length() == 0; + return s == null || s.isEmpty(); } public static boolean isEmpty(@Nullable CharSequence cs) { @@ -854,17 +865,17 @@ public class StringUtil extends StringUtilRt { } @NotNull - public static String notNullize(final String s, @NotNull String defaultValue) { + public static String notNullize(@Nullable final String s, @NotNull String defaultValue) { return s == null ? defaultValue : s; } @Nullable - public static String nullize(final @Nullable String s) { + public static String nullize(@Nullable final String s) { return nullize(s, false); } @Nullable - public static String nullize(final @Nullable String s, boolean nullizeSpaces) { + public static String nullize(@Nullable final String s, boolean nullizeSpaces) { if (nullizeSpaces) { if (isEmpty(s)) return null; } @@ -875,7 +886,7 @@ public class StringUtil extends StringUtilRt { } public static boolean isEmptyOrSpaces(@Nullable final String s) { - return s == null || s.trim().length() == 0; + return s == null || s.trim().isEmpty(); } /** @@ -960,7 +971,7 @@ public class StringUtil extends StringUtilRt { @NotNull public static List split(@NotNull String s, @NotNull String separator, boolean excludeSeparator, boolean excludeEmptyStrings) { - if (separator.length() == 0) { + if (separator.isEmpty()) { return Collections.singletonList(s); } List result = new ArrayList(); @@ -970,7 +981,7 @@ public class StringUtil extends StringUtilRt { if (index == -1) break; final int nextPos = index + separator.length(); String token = s.substring(pos, excludeSeparator ? index : nextPos); - if (token.length() != 0 || !excludeEmptyStrings) { + if (!token.isEmpty() || !excludeEmptyStrings) { result.add(token); } pos = nextPos; @@ -985,16 +996,21 @@ public class StringUtil extends StringUtilRt { public static Iterable tokenize(@NotNull String s, @NotNull String separators) { final com.intellij.util.text.StringTokenizer tokenizer = new com.intellij.util.text.StringTokenizer(s, separators); return new Iterable() { + @NotNull + @Override public Iterator iterator() { return new Iterator() { + @Override public boolean hasNext() { return tokenizer.hasMoreTokens(); } + @Override public String next() { return tokenizer.nextToken(); } + @Override public void remove() { throw new UnsupportedOperationException(); } @@ -1004,18 +1020,23 @@ public class StringUtil extends StringUtilRt { } @NotNull - public static Iterable tokenize(final StringTokenizer tokenizer) { + public static Iterable tokenize(@NotNull final StringTokenizer tokenizer) { return new Iterable() { + @NotNull + @Override public Iterator iterator() { return new Iterator() { + @Override public boolean hasNext() { return tokenizer.hasMoreTokens(); } + @Override public String next() { return tokenizer.nextToken(); } + @Override public void remove() { throw new UnsupportedOperationException(); } @@ -1119,7 +1140,7 @@ public class StringUtil extends StringUtilRt { final StringBuilder result = new StringBuilder(); for (T item : items) { String string = f.fun(item); - if (string != null && string.length() != 0) { + if (string != null && !string.isEmpty()) { if (result.length() != 0) result.append(separator); result.append(string); } @@ -1131,7 +1152,7 @@ public class StringUtil extends StringUtilRt { public static String join(@NotNull Collection strings, @NotNull final String separator) { final StringBuilder result = new StringBuilder(); for (String string : strings) { - if (string != null && string.length() != 0) { + if (string != null && !string.isEmpty()) { if (result.length() != 0) result.append(separator); result.append(string); } @@ -1150,7 +1171,7 @@ public class StringUtil extends StringUtilRt { } @NotNull - public static String join(final String... strings) { + public static String join(@Nullable final String... strings) { if (strings == null || strings.length == 0) return ""; final StringBuilder builder = StringBuilderSpinAllocator.alloc(); @@ -1254,6 +1275,7 @@ public class StringUtil extends StringUtilRt { return null; } + @Nullable private static String stripEnding(@NotNull String name, @NotNull String ending) { if (name.endsWith(ending)) { if (name.equals(ending)) return name; // do not return empty string @@ -1277,13 +1299,13 @@ public class StringUtil extends StringUtilRt { return false; } - public static boolean containsChar(final String value, final char ch) { + public static boolean containsChar(@NotNull final String value, final char ch) { return value.indexOf(ch) >= 0; } @Nullable public static String firstLetterToUpperCase(@Nullable final String displayString) { - if (displayString == null || displayString.length() == 0) return displayString; + if (displayString == null || displayString.isEmpty()) return displayString; char firstChar = displayString.charAt(0); char uppedFirstChar = toUpperCase(firstChar); @@ -1313,11 +1335,13 @@ public class StringUtil extends StringUtilRt { return result.toString(); } - public static List findMatches(String s, Pattern pattern) { + @NotNull + public static List findMatches(@NotNull String s, @NotNull Pattern pattern) { return findMatches(s, pattern, 1); } - public static List findMatches(String s, Pattern pattern, int groupIndex) { + @NotNull + public static List findMatches(@NotNull String s, @NotNull Pattern pattern, int groupIndex) { List result = new SmartList(); Matcher m = pattern.matcher(s); while (m.find()) { @@ -1435,7 +1459,7 @@ public class StringUtil extends StringUtilRt { * @return true if given symbol is contained at the target range of the given char sequence; * false otherwise */ - public static boolean contains(CharSequence s, int start, int end, char c) { + public static boolean contains(@NotNull CharSequence s, int start, int end, char c) { return indexOf(s, c, start, end) >= 0; } @@ -1476,17 +1500,18 @@ public class StringUtil extends StringUtilRt { return i + subString.length(); } - public static int indexOfAny(final String s, final String chars) { + public static int indexOfAny(@NotNull final String s, @NotNull final String chars) { return indexOfAny(s, chars, 0, s.length()); } - public static int indexOfAny(final String s, final String chars, final int start, final int end) { + public static int indexOfAny(@NotNull final String s, @NotNull final String chars, final int start, final int end) { for (int i = start; i < end; i++) { if (containsChar(chars, s.charAt(i))) return i; } return -1; } + @Nullable public static String substringAfter(@NotNull String text, @NotNull String subString) { int i = text.indexOf(subString); if (i == -1) return null; @@ -1578,7 +1603,7 @@ public class StringUtil extends StringUtilRt { } } - private static void unescapeSlashes(StringBuilder buf, String str) { + private static void unescapeSlashes(@NotNull StringBuilder buf, @NotNull String str) { final int length = str.length(); final int last = length - 1; for (int i = 0; i < length; i++) { @@ -1605,12 +1630,14 @@ public class StringUtil extends StringUtilRt { @NonNls private static final String[] REPLACES_REFS = {"<", ">", "&", "'", """}; @NonNls private static final String[] REPLACES_DISP = {"<", ">", "&", "'", "\""}; - public static String unescapeXml(final String text) { + @Nullable + public static String unescapeXml(@Nullable final String text) { if (text == null) return null; return replace(text, REPLACES_REFS, REPLACES_DISP); } - public static String escapeXml(final String text) { + @Nullable + public static String escapeXml(@Nullable final String text) { if (text == null) return null; return replace(text, REPLACES_DISP, REPLACES_REFS); } @@ -1659,14 +1686,14 @@ public class StringUtil extends StringUtilRt { public static String[] filterEmptyStrings(@NotNull String[] strings) { int emptyCount = 0; for (String string : strings) { - if (string == null || string.length() == 0) emptyCount++; + if (string == null || string.isEmpty()) emptyCount++; } if (emptyCount == 0) return strings; String[] result = ArrayUtil.newStringArray(strings.length - emptyCount); int count = 0; for (String string : strings) { - if (string == null || string.length() == 0) continue; + if (string == null || string.isEmpty()) continue; result[count++] = string; } @@ -1715,6 +1742,7 @@ public class StringUtil extends StringUtilRt { return r.toString(); } + @Nullable public static String getPropertyName(@NonNls @NotNull String methodName) { if (methodName.startsWith("get")) { return Introspector.decapitalize(methodName.substring(3)); @@ -1829,8 +1857,8 @@ public class StringUtil extends StringUtilRt { return escaped.toString(); } - public static String getQualifiedName(String packageName, String className) { - if (packageName == null || packageName.length() == 0) { + public static String getQualifiedName(@Nullable String packageName, String className) { + if (packageName == null || packageName.isEmpty()) { return className; } return packageName + '.' + className; @@ -1899,7 +1927,8 @@ public class StringUtil extends StringUtilRt { return "a" + Character.toUpperCase(c) + name.substring(1); } - public static String sanitizeJavaIdentifier(String name) { + @NotNull + public static String sanitizeJavaIdentifier(@NotNull String name) { final StringBuilder result = new StringBuilder(); for (int i = 0; i < name.length(); i++) { @@ -1983,11 +2012,11 @@ public class StringUtil extends StringUtilRt { * Implementation of "Sorting for Humans: Natural Sort Order": * http://www.codinghorror.com/blog/2007/12/sorting-for-humans-natural-sort-order.html */ - public static int naturalCompare(String string1, String string2) { + public static int naturalCompare(@NotNull String string1, @NotNull String string2) { return naturalCompare(string1, string2, false); } - private static int naturalCompare(String string1, String string2, boolean caseSensitive) { + private static int naturalCompare(@NotNull String string1, @NotNull String string2, boolean caseSensitive) { final int string1Length = string1.length(); final int string2Length = string2.length(); for (int i = 0, j = 0; i < string1Length && j < string2Length; i++, j++) { @@ -2058,7 +2087,7 @@ public class StringUtil extends StringUtilRt { } private static boolean isDigit(char c) { - return (c >= '0' && c <= '9'); + return c >= '0' && c <= '9'; } public static int compare(@Nullable String s1, @Nullable String s2, boolean ignoreCase) { @@ -2143,7 +2172,7 @@ public class StringUtil extends StringUtilRt { } @NotNull - public static String shortenTextWithEllipsis(final String text, final int max_length, final int suffix_length) { + public static String shortenTextWithEllipsis(@NotNull final String text, final int max_length, final int suffix_length) { final int prefix_length = max_length - suffix_length - 3; assert prefix_length > 0; final StringBuilder buffer = new StringBuilder(); diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/AbstractVcsHelper.java b/platform/vcs-api/src/com/intellij/openapi/vcs/AbstractVcsHelper.java index 8f36673d2417..12834d4124ed 100644 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/AbstractVcsHelper.java +++ b/platform/vcs-api/src/com/intellij/openapi/vcs/AbstractVcsHelper.java @@ -21,6 +21,8 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.vcs.annotate.AnnotationProvider; import com.intellij.openapi.vcs.annotate.FileAnnotation; import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.CommitExecutor; +import com.intellij.openapi.vcs.changes.LocalChangeList; import com.intellij.openapi.vcs.history.VcsFileRevision; import com.intellij.openapi.vcs.history.VcsHistoryProvider; import com.intellij.openapi.vcs.merge.MergeDialogCustomizer; @@ -162,4 +164,14 @@ public abstract class AbstractVcsHelper { @Nullable String cancelActionName) { return selectFilePathsToProcess(files, title, prompt, singleFileTitle, singleFilePromptTemplate, confirmationOption); }; + + + /** + * Shows commit dialog, fills it with the given changes and given commit message, initially selects the given changelist. + * @return true if user commits the changes, and commit succeeds; false if user presses Cancel or commit fails with errors. + */ + public abstract boolean commitChanges(@NotNull List changes, @NotNull LocalChangeList initialChangeList, + @NotNull String commitMessage, @Nullable CommitExecutor executor); + + } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/LocalChangeListImpl.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/LocalChangeListImpl.java index e61493548731..437146751815 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/LocalChangeListImpl.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/LocalChangeListImpl.java @@ -237,14 +237,8 @@ public class LocalChangeListImpl extends LocalChangeList { public synchronized boolean equals(final Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - final LocalChangeListImpl list = (LocalChangeListImpl)o; - - if (myIsDefault != list.myIsDefault) return false; - if (!myName.equals(list.myName)) return false; - if (myIsReadOnly != list.myIsReadOnly) return false; - - return true; + return myName.equals(list.myName); } public int hashCode() { diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/CommitHelper.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/CommitHelper.java index 436b293b1f29..f37a591b5fc7 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/CommitHelper.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/CommitHelper.java @@ -462,20 +462,42 @@ public class CommitHelper { } private void markCommittingDocuments() { - for (Change change : myIncludedChanges) { - Document doc = ChangesUtil.getFilePath(change).getDocument(); - if (doc != null) { - doc.putUserData(DOCUMENT_BEING_COMMITTED_KEY, myProject); - myCommittingDocuments.add(doc); - } - } + myCommittingDocuments.addAll(markCommittingDocuments(myProject, myIncludedChanges)); } private void unmarkCommittingDocuments() { - for (Document doc : myCommittingDocuments) { + unmarkCommittingDocuments(myCommittingDocuments); + myCommittingDocuments.clear(); + } + + /** + * Marks {@link Document documents} related to the given changes as "being committed". + * @return documents which were marked that way. + * @see #unmarkCommittingDocuments(java.util.Collection) + * @see VetoSavingCommittingDocumentsAdapter + */ + @NotNull + public static Collection markCommittingDocuments(@NotNull Project project, @NotNull List changes) { + Collection committingDocs = new ArrayList(); + for (Change change : changes) { + Document doc = ChangesUtil.getFilePath(change).getDocument(); + if (doc != null) { + doc.putUserData(DOCUMENT_BEING_COMMITTED_KEY, project); + committingDocs.add(doc); + } + } + return committingDocs; + } + + /** + * Removes the "being committed marker" from the given {@link Document documents}. + * @see #markCommittingDocuments(com.intellij.openapi.project.Project, java.util.List) + * @see VetoSavingCommittingDocumentsAdapter + */ + public static void unmarkCommittingDocuments(@NotNull Collection committingDocs) { + for (Document doc : committingDocs) { doc.putUserData(DOCUMENT_BEING_COMMITTED_KEY, null); } - myCommittingDocuments.clear(); } private void commitCompleted(final List allExceptions, final GeneralCommitProcessor processor) { diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/AbstractVcsHelperImpl.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/AbstractVcsHelperImpl.java index 1ae8907d3b65..52c75a6a300e 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/AbstractVcsHelperImpl.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/AbstractVcsHelperImpl.java @@ -49,6 +49,8 @@ import com.intellij.openapi.vcs.annotate.AnnotationProvider; import com.intellij.openapi.vcs.annotate.FileAnnotation; import com.intellij.openapi.vcs.changes.BackgroundFromStartOption; import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.CommitExecutor; +import com.intellij.openapi.vcs.changes.LocalChangeList; import com.intellij.openapi.vcs.changes.committed.*; import com.intellij.openapi.vcs.changes.ui.*; import com.intellij.openapi.vcs.history.*; @@ -218,6 +220,12 @@ public class AbstractVcsHelperImpl extends AbstractVcsHelper { } }); } + + @Override + public boolean commitChanges(@NotNull List changes, @NotNull LocalChangeList initialChangeList, + @NotNull String commitMessage, @Nullable CommitExecutor executor) { + return CommitChangeListDialog.commitChanges(myProject, changes, initialChangeList, executor, commitMessage); + } private void addDirectMessages(VcsErrorViewPanel vcsErrorViewPanel, List abstractVcsExceptions) { for (final VcsException exception : abstractVcsExceptions) { diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/projectlevelman/FileWatchRequestModifier.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/projectlevelman/FileWatchRequestModifier.java index 00c37e150e8c..b2fb77cc96aa 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/projectlevelman/FileWatchRequestModifier.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/projectlevelman/FileWatchRequestModifier.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2010 JetBrains s.r.o. + * Copyright 2000-2012 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,10 +19,7 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.vcs.VcsDirectoryMapping; import com.intellij.openapi.vfs.LocalFileSystem; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; +import java.util.*; /** * @author irengrig @@ -51,17 +48,25 @@ public class FileWatchRequestModifier implements Runnable { final List deleted = new LinkedList(myDirectoryMappingWatches.keySet()); deleted.removeAll(copy); + final Map toAdd = new HashMap(); for (VcsDirectoryMapping mapping : added) { - if (mapping.isDefaultMapping()) continue; - final LocalFileSystem.WatchRequest watchRequest = myLfs.addRootToWatch(mapping.getDirectory(), true); - myDirectoryMappingWatches.put(mapping, watchRequest); + if (!mapping.isDefaultMapping()) { + toAdd.put(mapping.getDirectory(), mapping); + } } + + final Collection toRemove = new ArrayList(); for (VcsDirectoryMapping mapping : deleted) { if (mapping.isDefaultMapping()) continue; final LocalFileSystem.WatchRequest removed = myDirectoryMappingWatches.remove(mapping); if (removed != null) { - myLfs.removeWatchedRoot(removed); + toRemove.add(removed); } } + + final Set requests = myLfs.replaceWatchedRoots(toRemove, toAdd.keySet(), true); + for (LocalFileSystem.WatchRequest request : requests) { + myDirectoryMappingWatches.put(toAdd.get(request.getRootPath()), request); + } } } diff --git a/plugins/IntentionPowerPak/src/com/siyeh/ipp/modifiers/ModifierIntention.java b/plugins/IntentionPowerPak/src/com/siyeh/ipp/modifiers/ModifierIntention.java index 6896839cdc93..9c3c91b908a4 100644 --- a/plugins/IntentionPowerPak/src/com/siyeh/ipp/modifiers/ModifierIntention.java +++ b/plugins/IntentionPowerPak/src/com/siyeh/ipp/modifiers/ModifierIntention.java @@ -59,20 +59,26 @@ abstract class ModifierIntention extends Intention implements LowPriorityAction return; } final MultiMap conflicts = checkForConflicts(member); - final ConflictsDialog conflictsDialog = new ConflictsDialog(member.getProject(), conflicts, new Runnable() { - @Override - public void run() { - final AccessToken token = WriteAction.start(); - try { - modifierList.setModifierProperty(getModifier(), true); + final boolean conflictsDialogOK; + if (conflicts.isEmpty()) { + conflictsDialogOK = true; + } else { + final ConflictsDialog conflictsDialog = new ConflictsDialog(member.getProject(), conflicts, new Runnable() { + @Override + public void run() { + final AccessToken token = WriteAction.start(); + try { + modifierList.setModifierProperty(getModifier(), true); + } + finally { + token.finish(); + } } - finally { - token.finish(); - } - } - }); - conflictsDialog.show(); - if (conflictsDialog.isOK()) { + }); + conflictsDialog.show(); + conflictsDialogOK = conflictsDialog.isOK(); + } + if (conflictsDialogOK) { modifierList.setModifierProperty(getModifier(), true); } } diff --git a/plugins/android-designer/src/com/intellij/android/designer/componentTree/AndroidTreeDecorator.java b/plugins/android-designer/src/com/intellij/android/designer/componentTree/AndroidTreeDecorator.java index 8c7f331803a7..bbbf6f6b5022 100644 --- a/plugins/android-designer/src/com/intellij/android/designer/componentTree/AndroidTreeDecorator.java +++ b/plugins/android-designer/src/com/intellij/android/designer/componentTree/AndroidTreeDecorator.java @@ -61,7 +61,10 @@ public final class AndroidTreeDecorator extends TreeComponentDecorator { value = ((RadViewComponent)component).getTag().getName(); } else { - value = StringUtil.shortenTextWithEllipsis(getPropertyValue(component, variable), 30, 5); + value = getPropertyValue(component, variable); + if (!StringUtil.isEmpty(value)) { + value = StringUtil.shortenTextWithEllipsis(value, 30, 5); + } } if (!StringUtil.isEmpty(value)) { diff --git a/plugins/android-designer/src/com/intellij/android/designer/designSurface/AbstractEditOperation.java b/plugins/android-designer/src/com/intellij/android/designer/designSurface/AbstractEditOperation.java index 517991ff94f0..442ce07ea644 100644 --- a/plugins/android-designer/src/com/intellij/android/designer/designSurface/AbstractEditOperation.java +++ b/plugins/android-designer/src/com/intellij/android/designer/designSurface/AbstractEditOperation.java @@ -21,15 +21,14 @@ import com.intellij.designer.designSurface.OperationContext; import com.intellij.designer.model.RadComponent; import org.jetbrains.annotations.Nullable; +import java.util.List; + /** * @author Alexander Lobas */ public abstract class AbstractEditOperation extends com.intellij.designer.designSurface.AbstractEditOperation { - protected final RadViewComponent myContainer; - - public AbstractEditOperation(RadViewComponent container, OperationContext context) { - super(context); - myContainer = container; + public AbstractEditOperation(RadComponent container, OperationContext context) { + super(container, context); } @Override @@ -38,19 +37,26 @@ public abstract class AbstractEditOperation extends com.intellij.designer.design } protected void execute(@Nullable RadViewComponent insertBefore) throws Exception { - if (myContext.isAdd() || myContext.isMove()) { - for (RadComponent component : myComponents) { - ModelParser.moveComponent(myContainer, (RadViewComponent)component, insertBefore); + execute(myContext, (RadViewComponent)myContainer, myComponents, insertBefore); + } + + public static void execute(OperationContext context, + RadViewComponent container, + List components, + @Nullable RadViewComponent insertBefore) throws Exception { + if (context.isAdd() || context.isMove()) { + for (RadComponent component : components) { + ModelParser.moveComponent(container, (RadViewComponent)component, insertBefore); } } - else if (myContext.isCreate()) { - for (RadComponent component : myComponents) { - ModelParser.addComponent(myContainer, (RadViewComponent)component, insertBefore); + else if (context.isCreate()) { + for (RadComponent component : components) { + ModelParser.addComponent(container, (RadViewComponent)component, insertBefore); } } - else if (myContext.isPaste()) { - for (RadComponent component : myComponents) { - ModelParser.pasteComponent(myContainer, (RadViewComponent)component, insertBefore); + else if (context.isPaste()) { + for (RadComponent component : components) { + ModelParser.pasteComponent(container, (RadViewComponent)component, insertBefore); } } } diff --git a/plugins/android-designer/src/com/intellij/android/designer/designSurface/DropToOperation.java b/plugins/android-designer/src/com/intellij/android/designer/designSurface/DropToOperation.java index 044075491a6d..8d0f9bafa479 100644 --- a/plugins/android-designer/src/com/intellij/android/designer/designSurface/DropToOperation.java +++ b/plugins/android-designer/src/com/intellij/android/designer/designSurface/DropToOperation.java @@ -15,10 +15,10 @@ */ package com.intellij.android.designer.designSurface; -import com.intellij.android.designer.model.RadViewComponent; import com.intellij.designer.designSurface.FeedbackLayer; import com.intellij.designer.designSurface.OperationContext; import com.intellij.designer.designSurface.feedbacks.AlphaFeedback; +import com.intellij.designer.model.RadComponent; import javax.swing.*; import java.awt.*; @@ -29,7 +29,7 @@ import java.awt.*; public class DropToOperation extends AbstractEditOperation { private JComponent myFeedback; - public DropToOperation(RadViewComponent container, OperationContext context) { + public DropToOperation(RadComponent container, OperationContext context) { super(container, context); } diff --git a/plugins/android-designer/src/com/intellij/android/designer/designSurface/TreeDropToOperation.java b/plugins/android-designer/src/com/intellij/android/designer/designSurface/TreeDropToOperation.java index c9dc4f1f5158..69c8282c6763 100644 --- a/plugins/android-designer/src/com/intellij/android/designer/designSurface/TreeDropToOperation.java +++ b/plugins/android-designer/src/com/intellij/android/designer/designSurface/TreeDropToOperation.java @@ -15,34 +15,22 @@ */ package com.intellij.android.designer.designSurface; -import com.intellij.android.designer.model.ModelParser; import com.intellij.android.designer.model.RadViewComponent; import com.intellij.designer.componentTree.TreeEditOperation; import com.intellij.designer.designSurface.OperationContext; import com.intellij.designer.model.RadComponent; +import org.jetbrains.annotations.Nullable; /** * @author Alexander Lobas */ public class TreeDropToOperation extends TreeEditOperation { - public TreeDropToOperation(RadComponent host, OperationContext context) { - super(host, context); + public TreeDropToOperation(RadComponent container, OperationContext context) { + super(container, context); } @Override - protected void execute(RadComponent insertBefore) throws Exception { - RadViewComponent host = (RadViewComponent)myHost; - RadViewComponent before = (RadViewComponent)insertBefore; - - if (myContext.isAdd() || myContext.isMove()) { - for (RadComponent component : myComponents) { - ModelParser.moveComponent(host, (RadViewComponent)component, before); - } - } - else { - for (RadComponent component : myComponents) { - ModelParser.addComponent(host, (RadViewComponent)component, before); - } - } + protected void execute(@Nullable RadComponent insertBefore) throws Exception { + AbstractEditOperation.execute(myContext, (RadViewComponent)myContainer, myComponents, (RadViewComponent)insertBefore); } } \ No newline at end of file diff --git a/plugins/android-designer/src/com/intellij/android/designer/designSurface/layout/AbsoluteLayoutOperation.java b/plugins/android-designer/src/com/intellij/android/designer/designSurface/layout/AbsoluteLayoutOperation.java index f09bde5d6dd8..6943eca67e46 100644 --- a/plugins/android-designer/src/com/intellij/android/designer/designSurface/layout/AbsoluteLayoutOperation.java +++ b/plugins/android-designer/src/com/intellij/android/designer/designSurface/layout/AbsoluteLayoutOperation.java @@ -35,7 +35,7 @@ public class AbsoluteLayoutOperation extends AbstractEditOperation { private Rectangle myBounds; private Point myStartLocation; - public AbsoluteLayoutOperation(RadViewComponent container, OperationContext context) { + public AbsoluteLayoutOperation(RadComponent container, OperationContext context) { super(container, context); } diff --git a/plugins/android-designer/src/com/intellij/android/designer/designSurface/layout/FlowBaseOperation.java b/plugins/android-designer/src/com/intellij/android/designer/designSurface/layout/FlowBaseOperation.java index 017dc6d6cf2f..bb4c2555f7a8 100644 --- a/plugins/android-designer/src/com/intellij/android/designer/designSurface/layout/FlowBaseOperation.java +++ b/plugins/android-designer/src/com/intellij/android/designer/designSurface/layout/FlowBaseOperation.java @@ -19,170 +19,32 @@ import com.intellij.android.designer.designSurface.AbstractEditOperation; import com.intellij.android.designer.model.RadViewComponent; import com.intellij.designer.designSurface.FeedbackLayer; import com.intellij.designer.designSurface.OperationContext; -import com.intellij.designer.designSurface.feedbacks.LineInsertFeedback; -import com.intellij.designer.designSurface.feedbacks.RectangleFeedback; import com.intellij.designer.model.RadComponent; +import org.jetbrains.annotations.Nullable; import java.awt.*; -import java.util.List; /** * @author Alexander Lobas */ -public class FlowBaseOperation extends AbstractEditOperation { - protected final boolean myHorizontal; - protected RectangleFeedback myFirstInsertFeedback; - protected LineInsertFeedback myInsertFeedback; - protected Rectangle myBounds; - protected RadComponent myChildTarget; - protected boolean myInsertBefore; - - public FlowBaseOperation(RadViewComponent container, OperationContext context, boolean horizontal) { - super(container, context); - myHorizontal = horizontal; - } - - protected void createFeedback() { - if (myFirstInsertFeedback == null) { - FeedbackLayer layer = myContext.getArea().getFeedbackLayer(); - myBounds = myContainer.getBounds(layer); - - myFirstInsertFeedback = new RectangleFeedback(Color.green, 2); - myFirstInsertFeedback.setBounds(myBounds); - - myInsertFeedback = new LineInsertFeedback(Color.green, !myHorizontal); - myInsertFeedback.size(myBounds.width, myBounds.height); - - if (myContainer.getChildren().isEmpty()) { - layer.add(myFirstInsertFeedback); - } - else { - layer.add(myInsertFeedback); - } - layer.repaint(); - } +public class FlowBaseOperation extends com.intellij.designer.designSurface.FlowBaseOperation { + public FlowBaseOperation(RadComponent container, OperationContext context, boolean horizontal) { + super(container, context, horizontal); } @Override - public void showFeedback() { - createFeedback(); - - if (!myContainer.getChildren().isEmpty()) { - FeedbackLayer layer = myContext.getArea().getFeedbackLayer(); - Point location = myContext.getLocation(); - myChildTarget = null; - - if (myHorizontal) { - for (RadComponent child : myContainer.getChildren()) { - Rectangle childBounds = child.getBounds(layer); - Rectangle margins = ((RadViewComponent)child).getMargins(); - if (childBounds.x - margins.x <= location.x && location.x <= childBounds.getMaxX() + margins.width) { - myChildTarget = child; - break; - } - } - } - else { - for (RadComponent child : myContainer.getChildren()) { - Rectangle childBounds = child.getBounds(layer); - Rectangle margins = ((RadViewComponent)child).getMargins(); - if (childBounds.y - margins.y <= location.y && location.y <= childBounds.getMaxY() + margins.height) { - myChildTarget = child; - break; - } - } - } - if (myChildTarget == null) { - myChildTarget = getSideChildTarget(); - } - - Rectangle targetBounds = myChildTarget.getBounds(layer); - Rectangle targetMargins = ((RadViewComponent)myChildTarget).getMargins(); - if (myHorizontal) { - myInsertBefore = location.x < targetBounds.getCenterX(); - - if (myInsertBefore) { - myInsertFeedback.setLocation(targetBounds.x - targetMargins.x, myBounds.y); - } - else { - myInsertFeedback.setLocation(targetBounds.x + targetBounds.width + targetMargins.width, myBounds.y); - } - } - else { - myInsertBefore = location.y < targetBounds.getCenterY(); - - if (myInsertBefore) { - myInsertFeedback.setLocation(myBounds.x, targetBounds.y - targetMargins.y); - } - else { - myInsertFeedback.setLocation(myBounds.x, targetBounds.y + targetBounds.height + targetMargins.height); - } - } - - layer.repaint(); - } - } - - private RadComponent getSideChildTarget() { - Point location = myContext.getLocation(); - List children = myContainer.getChildren(); - RadComponent lastChild = children.get(children.size() - 1); - Rectangle childBounds = lastChild.getBounds(myContext.getArea().getFeedbackLayer()); - - if (myHorizontal) { - if (location.x >= childBounds.getMaxX()) { - return lastChild; - } - return children.get(0); - } - if (location.y >= childBounds.getMaxY()) { - return lastChild; - } - return children.get(0); + protected Rectangle getBounds(RadComponent component, FeedbackLayer layer) { + Rectangle bounds = component.getBounds(layer); + Rectangle margins = ((RadViewComponent)component).getMargins(); + bounds.x -= margins.x; + bounds.y -= margins.y; + bounds.width += margins.x + margins.width; + bounds.height += margins.y + margins.height; + return bounds; } @Override - public void eraseFeedback() { - if (myFirstInsertFeedback != null) { - FeedbackLayer layer = myContext.getArea().getFeedbackLayer(); - layer.remove(myFirstInsertFeedback); - layer.remove(myInsertFeedback); - layer.repaint(); - myFirstInsertFeedback = null; - myInsertFeedback = null; - } - } - - @Override - public boolean canExecute() { - if (myContext.isMove()) { - if (myComponents.contains(myChildTarget)) { - return false; - } - - List children = myContainer.getChildren(); - int index = children.indexOf(myChildTarget) + (myInsertBefore ? -1 : 1); - if (0 <= index && index < children.size()) { - return !myComponents.contains(children.get(index)); - } - } - return true; - } - - @Override - public void execute() throws Exception { - if (myChildTarget == null || myInsertBefore) { - execute((RadViewComponent)myChildTarget); - } - else { - List children = myContainer.getChildren(); - int index = children.indexOf(myChildTarget) + 1; - if (index < children.size()) { - execute((RadViewComponent)children.get(index)); - } - else { - execute(null); - } - } + protected void execute(@Nullable RadComponent insertBefore) throws Exception { + AbstractEditOperation.execute(myContext, (RadViewComponent)myContainer, myComponents, (RadViewComponent)insertBefore); } } \ No newline at end of file diff --git a/plugins/android-designer/src/com/intellij/android/designer/designSurface/layout/FrameLayoutOperation.java b/plugins/android-designer/src/com/intellij/android/designer/designSurface/layout/FrameLayoutOperation.java index bab0feb86d6e..146686f8bb13 100644 --- a/plugins/android-designer/src/com/intellij/android/designer/designSurface/layout/FrameLayoutOperation.java +++ b/plugins/android-designer/src/com/intellij/android/designer/designSurface/layout/FrameLayoutOperation.java @@ -44,7 +44,7 @@ public class FrameLayoutOperation extends AbstractEditOperation { private Rectangle myBounds; private String myGravity; - public FrameLayoutOperation(RadViewComponent container, OperationContext context) { + public FrameLayoutOperation(RadComponent container, OperationContext context) { super(container, context); if (context.isMove()) { diff --git a/plugins/android-designer/src/com/intellij/android/designer/designSurface/layout/LinearLayoutOperation.java b/plugins/android-designer/src/com/intellij/android/designer/designSurface/layout/LinearLayoutOperation.java index 265f30c70cbc..0e5a73bff3ad 100644 --- a/plugins/android-designer/src/com/intellij/android/designer/designSurface/layout/LinearLayoutOperation.java +++ b/plugins/android-designer/src/com/intellij/android/designer/designSurface/layout/LinearLayoutOperation.java @@ -41,7 +41,7 @@ public class LinearLayoutOperation extends FlowBaseOperation { private Gravity myExclude; private Gravity myGravity; - public LinearLayoutOperation(RadViewComponent container, OperationContext context, boolean horizontal) { + public LinearLayoutOperation(RadComponent container, OperationContext context, boolean horizontal) { super(container, context, horizontal); if (context.isMove() && context.getComponents().size() == 1) { diff --git a/plugins/android-designer/src/com/intellij/android/designer/designSurface/layout/ResizeOperation.java b/plugins/android-designer/src/com/intellij/android/designer/designSurface/layout/ResizeOperation.java index 7b0e533f2d0d..eef3e74d7b4c 100644 --- a/plugins/android-designer/src/com/intellij/android/designer/designSurface/layout/ResizeOperation.java +++ b/plugins/android-designer/src/com/intellij/android/designer/designSurface/layout/ResizeOperation.java @@ -71,6 +71,10 @@ public class ResizeOperation implements EditOperation { decorator.addPoint(new DirectionResizePoint(blue, Color.black, Position.SOUTH_EAST, TYPE, "Change layout:width x layout:height")); } + public static void height(ResizeSelectionDecorator decorator) { + decorator.addPoint(new DirectionResizePoint(blue, Color.black, Position.SOUTH, TYPE, "Change layout:height")); + } + @Override public void setComponent(RadComponent component) { myComponent = (RadViewComponent)component; @@ -327,11 +331,9 @@ public class ResizeOperation implements EditOperation { if (size == wrap) { return "wrap_content"; } - else if (size == fill) { + if (size == fill) { return "fill_parent"; } - else { - return Integer.toString(size) + "dp"; - } + return Integer.toString(size) + "dp"; } } \ No newline at end of file diff --git a/plugins/android-designer/src/com/intellij/android/designer/model/ModelParser.java b/plugins/android-designer/src/com/intellij/android/designer/model/ModelParser.java index 02177dc08263..181b45a1c5f4 100644 --- a/plugins/android-designer/src/com/intellij/android/designer/model/ModelParser.java +++ b/plugins/android-designer/src/com/intellij/android/designer/model/ModelParser.java @@ -154,7 +154,7 @@ public class ModelParser extends XmlRecursiveElementVisitor { } xmlTag.delete(); - movedComponent.setTag(newXmlTag); + movedComponent.updateTag(newXmlTag); } }); diff --git a/plugins/android-designer/src/com/intellij/android/designer/model/RadSingleChildrenViewLayout.java b/plugins/android-designer/src/com/intellij/android/designer/model/RadSingleChildrenViewLayout.java index c7e2e0e3aef5..9d004a1e40b3 100644 --- a/plugins/android-designer/src/com/intellij/android/designer/model/RadSingleChildrenViewLayout.java +++ b/plugins/android-designer/src/com/intellij/android/designer/model/RadSingleChildrenViewLayout.java @@ -17,6 +17,7 @@ package com.intellij.android.designer.model; import com.intellij.android.designer.designSurface.DropToOperation; import com.intellij.android.designer.designSurface.TreeDropToOperation; +import com.intellij.designer.componentTree.TreeEditOperation; import com.intellij.designer.designSurface.EditOperation; import com.intellij.designer.designSurface.OperationContext; @@ -30,9 +31,12 @@ public class RadSingleChildrenViewLayout extends RadViewLayout { (context.isCreate() || context.isPaste() || context.isAdd()) && context.getComponents().size() == 1) { if (context.isTree()) { - return new TreeDropToOperation(myContainer, context); + if (TreeEditOperation.isTarget(myContainer, context)) { + return new TreeDropToOperation(myContainer, context); + } + return null; } - return new DropToOperation((RadViewComponent)myContainer, context); + return new DropToOperation(myContainer, context); } return null; diff --git a/plugins/android-designer/src/com/intellij/android/designer/model/RadViewComponent.java b/plugins/android-designer/src/com/intellij/android/designer/model/RadViewComponent.java index 6b5cbee4de3f..4335fee51b22 100644 --- a/plugins/android-designer/src/com/intellij/android/designer/model/RadViewComponent.java +++ b/plugins/android-designer/src/com/intellij/android/designer/model/RadViewComponent.java @@ -53,6 +53,18 @@ public class RadViewComponent extends RadVisualComponent { myTag = tag; } + public void updateTag(XmlTag tag) { + setTag(tag); + + int size = myChildren.size(); + XmlTag[] tags = tag.getSubTags(); + + for (int i = 0; i < size; i++) { + RadViewComponent child = (RadViewComponent)myChildren.get(i); + child.updateTag(tags[i]); + } + } + public ViewInfo getViewInfo() { return myViewInfo; } diff --git a/plugins/android-designer/src/com/intellij/android/designer/model/layout/RadAbsoluteLayout.java b/plugins/android-designer/src/com/intellij/android/designer/model/layout/RadAbsoluteLayout.java index 5f8b525c7f7f..bea6245f5eac 100644 --- a/plugins/android-designer/src/com/intellij/android/designer/model/layout/RadAbsoluteLayout.java +++ b/plugins/android-designer/src/com/intellij/android/designer/model/layout/RadAbsoluteLayout.java @@ -18,8 +18,8 @@ package com.intellij.android.designer.model.layout; import com.intellij.android.designer.designSurface.TreeDropToOperation; import com.intellij.android.designer.designSurface.layout.AbsoluteLayoutOperation; import com.intellij.android.designer.designSurface.layout.ResizeOperation; -import com.intellij.android.designer.model.RadViewComponent; import com.intellij.android.designer.model.RadViewLayoutWithData; +import com.intellij.designer.componentTree.TreeEditOperation; import com.intellij.designer.designSurface.ComponentDecorator; import com.intellij.designer.designSurface.EditOperation; import com.intellij.designer.designSurface.OperationContext; @@ -48,9 +48,12 @@ public class RadAbsoluteLayout extends RadViewLayoutWithData { public EditOperation processChildOperation(OperationContext context) { if (context.isCreate() || context.isPaste() || context.isAdd() || context.isMove()) { if (context.isTree()) { - return new TreeDropToOperation(myContainer, context); + if (TreeEditOperation.isTarget(myContainer, context)) { + return new TreeDropToOperation(myContainer, context); + } + return null; } - return new AbsoluteLayoutOperation((RadViewComponent)myContainer, context); + return new AbsoluteLayoutOperation(myContainer, context); } if (context.is(ResizeOperation.TYPE)) { return new ResizeOperation(context); diff --git a/plugins/android-designer/src/com/intellij/android/designer/model/layout/RadFrameLayout.java b/plugins/android-designer/src/com/intellij/android/designer/model/layout/RadFrameLayout.java index 0e56cc3d3a34..484c03f8dc01 100644 --- a/plugins/android-designer/src/com/intellij/android/designer/model/layout/RadFrameLayout.java +++ b/plugins/android-designer/src/com/intellij/android/designer/model/layout/RadFrameLayout.java @@ -22,6 +22,7 @@ import com.intellij.android.designer.designSurface.layout.ResizeOperation; import com.intellij.android.designer.model.RadViewComponent; import com.intellij.android.designer.model.RadViewLayoutWithData; import com.intellij.designer.actions.AbstractComboBoxAction; +import com.intellij.designer.componentTree.TreeEditOperation; import com.intellij.designer.designSurface.ComponentDecorator; import com.intellij.designer.designSurface.DesignerEditorPanel; import com.intellij.designer.designSurface.EditOperation; @@ -61,14 +62,17 @@ public class RadFrameLayout extends RadViewLayoutWithData { public EditOperation processChildOperation(OperationContext context) { if (context.isCreate() || context.isPaste() || context.isAdd() || context.isMove()) { if (context.isTree()) { - return new TreeDropToOperation(myContainer, context); + if (TreeEditOperation.isTarget(myContainer, context)) { + return new TreeDropToOperation(myContainer, context); + } + return null; } - return new FrameLayoutOperation((RadViewComponent)myContainer, context); + return new FrameLayoutOperation(myContainer, context); } - else if (context.is(ResizeOperation.TYPE)) { + if (context.is(ResizeOperation.TYPE)) { return new ResizeOperation(context); } - else if (context.is(LayoutMarginOperation.TYPE)) { + if (context.is(LayoutMarginOperation.TYPE)) { return new LayoutMarginOperation(context); } return null; diff --git a/plugins/android-designer/src/com/intellij/android/designer/model/layout/RadLinearLayout.java b/plugins/android-designer/src/com/intellij/android/designer/model/layout/RadLinearLayout.java index 12cdc9fd9bb3..139eb8255912 100644 --- a/plugins/android-designer/src/com/intellij/android/designer/model/layout/RadLinearLayout.java +++ b/plugins/android-designer/src/com/intellij/android/designer/model/layout/RadLinearLayout.java @@ -20,6 +20,7 @@ import com.intellij.android.designer.designSurface.layout.*; import com.intellij.android.designer.model.RadViewComponent; import com.intellij.android.designer.model.RadViewLayoutWithData; import com.intellij.designer.actions.AbstractComboBoxAction; +import com.intellij.designer.componentTree.TreeEditOperation; import com.intellij.designer.designSurface.*; import com.intellij.designer.designSurface.selection.DirectionResizePoint; import com.intellij.designer.designSurface.selection.ResizePoint; @@ -69,17 +70,20 @@ public class RadLinearLayout extends RadViewLayoutWithData implements ILayoutDec public EditOperation processChildOperation(OperationContext context) { if (context.isCreate() || context.isPaste() || context.isAdd() || context.isMove()) { if (context.isTree()) { - return new TreeDropToOperation(myContainer, context); + if (TreeEditOperation.isTarget(myContainer, context)) { + return new TreeDropToOperation(myContainer, context); + } + return null; } - return new LinearLayoutOperation((RadViewComponent)myContainer, context, isHorizontal()); + return new LinearLayoutOperation(myContainer, context, isHorizontal()); } - else if (context.is(ResizeOperation.TYPE)) { + if (context.is(ResizeOperation.TYPE)) { return new ResizeOperation(context); } - else if (context.is(LayoutMarginOperation.TYPE)) { + if (context.is(LayoutMarginOperation.TYPE)) { return new LayoutMarginOperation(context); } - else if (context.is(LayoutWeightOperation.TYPE)) { + if (context.is(LayoutWeightOperation.TYPE)) { return new LayoutWeightOperation(context); } return null; diff --git a/plugins/android-designer/src/com/intellij/android/designer/model/table/RadCaptionTableColumn.java b/plugins/android-designer/src/com/intellij/android/designer/model/table/RadCaptionTableColumn.java index 5b2ebdd08bc6..92952bf54b02 100644 --- a/plugins/android-designer/src/com/intellij/android/designer/model/table/RadCaptionTableColumn.java +++ b/plugins/android-designer/src/com/intellij/android/designer/model/table/RadCaptionTableColumn.java @@ -16,7 +16,6 @@ package com.intellij.android.designer.model.table; import com.intellij.android.designer.designSurface.layout.CaptionStaticDecorator; -import com.intellij.designer.designSurface.EditableArea; import com.intellij.designer.designSurface.StaticDecorator; import com.intellij.designer.model.RadComponent; import com.intellij.designer.model.RadVisualComponent; @@ -33,7 +32,7 @@ public class RadCaptionTableColumn extends RadVisualComponent { private final int myColumnWidth; private final StaticDecorator myDecorator = new CaptionStaticDecorator(this); - public RadCaptionTableColumn(RadTableLayoutComponent container, int columnOffset, int columnWidth, EditableArea mainArea) { + public RadCaptionTableColumn(RadTableLayoutComponent container, int columnOffset, int columnWidth) { myContainer = container; myColumnOffset = columnOffset; myColumnWidth = columnWidth; @@ -43,13 +42,13 @@ public class RadCaptionTableColumn extends RadVisualComponent { @Override public Rectangle getBounds() { Rectangle bounds = myContainer.getBounds(); - return new Rectangle(bounds.x + myColumnOffset, 3, myColumnWidth, 10); + return new Rectangle(bounds.x + myColumnOffset, 2, myColumnWidth, 10); } @Override public Rectangle getBounds(Component relativeTo) { Rectangle bounds = super.getBounds(relativeTo); - bounds.y = 3; + bounds.y = 2; bounds.height = 10; return bounds; } diff --git a/plugins/android-designer/src/com/intellij/android/designer/model/table/RadCaptionTableRow.java b/plugins/android-designer/src/com/intellij/android/designer/model/table/RadCaptionTableRow.java index 6dfb3f655664..f2e7416b43b9 100644 --- a/plugins/android-designer/src/com/intellij/android/designer/model/table/RadCaptionTableRow.java +++ b/plugins/android-designer/src/com/intellij/android/designer/model/table/RadCaptionTableRow.java @@ -17,7 +17,6 @@ package com.intellij.android.designer.model.table; import com.intellij.android.designer.designSurface.layout.CaptionStaticDecorator; import com.intellij.android.designer.model.RadViewComponent; -import com.intellij.designer.designSurface.EditableArea; import com.intellij.designer.designSurface.StaticDecorator; import com.intellij.designer.model.RadComponent; import com.intellij.designer.model.RadVisualComponent; @@ -32,21 +31,25 @@ public class RadCaptionTableRow extends RadVisualComponent { private final RadViewComponent myComponent; private final StaticDecorator myDecorator = new CaptionStaticDecorator(this); - public RadCaptionTableRow(RadTableLayoutComponent container, RadViewComponent component, EditableArea mainArea) { + public RadCaptionTableRow(RadViewComponent component) { myComponent = component; - setNativeComponent(container.getNativeComponent()); + setNativeComponent(component.getNativeComponent()); + } + + public RadViewComponent getComponent() { + return myComponent; } @Override public Rectangle getBounds() { Rectangle bounds = myComponent.getBounds(); - return new Rectangle(3, bounds.y, 10, bounds.height); + return new Rectangle(2, bounds.y, 10, bounds.height); } @Override public Rectangle getBounds(Component relativeTo) { Rectangle bounds = super.getBounds(relativeTo); - bounds.x = 3; + bounds.x = 2; bounds.width = 10; return bounds; } diff --git a/plugins/android-designer/src/com/intellij/android/designer/model/table/RadTableLayout.java b/plugins/android-designer/src/com/intellij/android/designer/model/table/RadTableLayout.java index d9c36fb8b8bd..0275805744c3 100644 --- a/plugins/android-designer/src/com/intellij/android/designer/model/table/RadTableLayout.java +++ b/plugins/android-designer/src/com/intellij/android/designer/model/table/RadTableLayout.java @@ -15,18 +15,24 @@ */ package com.intellij.android.designer.model.table; -import com.intellij.android.designer.designSurface.TreeDropToOperation; +import com.intellij.android.designer.designSurface.*; +import com.intellij.android.designer.designSurface.AbstractEditOperation; +import com.intellij.android.designer.designSurface.layout.ResizeOperation; import com.intellij.android.designer.designSurface.layout.TableLayoutDecorator; import com.intellij.android.designer.model.RadViewComponent; import com.intellij.android.designer.model.RadViewLayout; import com.intellij.android.designer.model.RadViewLayoutWithData; +import com.intellij.designer.componentTree.TreeEditOperation; import com.intellij.designer.designSurface.*; +import com.intellij.designer.designSurface.selection.ResizeSelectionDecorator; import com.intellij.designer.model.RadComponent; import com.intellij.designer.model.RadLayout; import com.intellij.openapi.actionSystem.DefaultActionGroup; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; +import java.awt.*; import java.util.ArrayList; import java.util.List; @@ -37,6 +43,7 @@ public class RadTableLayout extends RadViewLayoutWithData implements ILayoutDeco private static final String[] LAYOUT_PARAMS = {"", "LinearLayout_Layout", "ViewGroup_MarginLayout"}; private TableLayoutDecorator myGridDecorator; + private ResizeSelectionDecorator myResizeDecorator; @Override @NotNull @@ -48,11 +55,17 @@ public class RadTableLayout extends RadViewLayoutWithData implements ILayoutDeco public EditOperation processChildOperation(OperationContext context) { if (context.isCreate() || context.isPaste() || context.isAdd() || context.isMove()) { if (context.isTree()) { - return new TreeDropToOperation(myContainer, context); + if (TreeEditOperation.isTarget(myContainer, context)) { + return new TreeDropToOperation(myContainer, context); + } + return null; } // XXX + return null; + } + if (context.is(ResizeOperation.TYPE)) { + return new ResizeOperation(context); } - // XXX return null; } @@ -84,7 +97,15 @@ public class RadTableLayout extends RadViewLayoutWithData implements ILayoutDeco @Override public ComponentDecorator getChildSelectionDecorator(RadComponent component, List selection) { - return super.getChildSelectionDecorator(component, selection); // TODO: Auto-generated method stub + if (component.getLayout() instanceof RadTableRowLayout) { + return super.getChildSelectionDecorator(component, selection); + } + + if (myResizeDecorator == null) { + myResizeDecorator = new ResizeSelectionDecorator(Color.red, 1); + ResizeOperation.height(myResizeDecorator); + } + return myResizeDecorator; } ////////////////////////////////////////////////////////////////////////////////////////// @@ -120,12 +141,6 @@ public class RadTableLayout extends RadViewLayoutWithData implements ILayoutDeco return myContainer == component && myContainer.getParent().getLayout() instanceof ICaptionDecorator ? null : this; } - @Override - @NotNull - public RadLayout getCaptionLayout(EditableArea mainArea, boolean horizontal) { - return RadViewLayout.INSTANCE; - } - @Override @NotNull public List getCaptionChildren(EditableArea mainArea, boolean horizontal) { @@ -135,28 +150,127 @@ public class RadTableLayout extends RadViewLayoutWithData implements ILayoutDeco if (horizontal) { if (children.isEmpty()) { - components.add(new RadCaptionTableColumn(container, 0, container.getBounds().width, mainArea)); + components.add(new RadCaptionTableColumn(container, 0, container.getBounds().width)); } else { int columnOffset = 0; for (int columnWidth : container.getColumnWidths()) { - components.add(new RadCaptionTableColumn(container, columnOffset, Math.max(columnWidth, 2), mainArea)); - - if (columnWidth > 0) { - columnOffset += columnWidth; - } + components.add(new RadCaptionTableColumn(container, columnOffset, Math.max(columnWidth, 2))); + columnOffset += columnWidth; } } } else if (children.isEmpty()) { - components.add(new RadCaptionTableRow(container, container, mainArea)); + components.add(new RadCaptionTableRow(container)); } else { for (RadComponent component : children) { - components.add(new RadCaptionTableRow(container, (RadViewComponent)component, mainArea)); + components.add(new RadCaptionTableRow((RadViewComponent)component)); } } return components; } + + private RadLayout myCaptionColumnLayout = RadViewLayout.INSTANCE; + private RadLayout myCaptionRowLayout; + + @Override + @NotNull + public RadLayout getCaptionLayout(final EditableArea mainArea, boolean horizontal) { + if (horizontal) { + return myCaptionColumnLayout; + } + + if (myCaptionRowLayout == null) { + myCaptionRowLayout = new RadViewLayout() { + @Override + public EditOperation processChildOperation(OperationContext context) { + if (context.isMove()) { + return new CaptionFlowBaseOperation(myContainer, context, false, mainArea) { + @Override + public void setComponents(List components) { + super.setComponents(components); + myMainContext.setComponents(getRowComponents(components)); + } + + @Override + public void showFeedback() { + myMainContext.setLocation(SwingUtilities.convertPoint(myContext.getArea().getFeedbackLayer(), myContext.getLocation(), + myMainContext.getArea().getFeedbackLayer())); + super.showFeedback(); + } + + @Override + protected void execute(@Nullable RadComponent insertBefore) throws Exception { + super.execute(insertBefore); + AbstractEditOperation.execute(myMainContext, + (RadViewComponent)RadTableLayout.this.myContainer, + getRowComponents(myComponents), + getRowComponent(insertBefore)); + } + }; + } + return null; + } + }; + } + return myCaptionRowLayout; + } + + private static List getRowComponents(List components) { + List rowComponents = new ArrayList(); + for (RadComponent component : components) { + rowComponents.add(((RadCaptionTableRow)component).getComponent()); + } + return rowComponents; + } + + @Nullable + private static RadViewComponent getRowComponent(@Nullable RadComponent component) { + RadCaptionTableRow row = (RadCaptionTableRow)component; + return row == null ? null : row.getComponent(); + } + + private abstract class CaptionFlowBaseOperation extends FlowBaseOperation { + protected OperationContext myMainContext; + protected FlowBaseOperation myMainOperation; + + public CaptionFlowBaseOperation(RadComponent container, OperationContext context, boolean horizontal, EditableArea mainArea) { + super(container, context, horizontal); + myMainContext = new OperationContext(context.getType()); + myMainContext.setArea(mainArea); + myMainOperation = new MainFlowBaseOperation(RadTableLayout.this.myContainer, myMainContext, horizontal); + } + + @Override + public void showFeedback() { + super.showFeedback(); + myMainOperation.showFeedback(); + } + + @Override + public void eraseFeedback() { + super.eraseFeedback(); + myMainOperation.eraseFeedback(); + } + + @Override + protected void execute(@Nullable RadComponent insertBefore) throws Exception { + for (RadComponent component : myComponents) { + component.removeFromParent(); + myContainer.add(component, insertBefore); + } + } + } + + private static class MainFlowBaseOperation extends FlowBaseOperation { + public MainFlowBaseOperation(RadComponent container, OperationContext context, boolean horizontal) { + super(container, context, horizontal); + } + + @Override + protected void execute(@Nullable RadComponent insertBefore) throws Exception { + } + } } \ No newline at end of file diff --git a/plugins/android-designer/src/com/intellij/android/designer/model/viewAnimator/RadViewAnimatorLayout.java b/plugins/android-designer/src/com/intellij/android/designer/model/viewAnimator/RadViewAnimatorLayout.java index 0a3cc527c656..c333c2157112 100644 --- a/plugins/android-designer/src/com/intellij/android/designer/model/viewAnimator/RadViewAnimatorLayout.java +++ b/plugins/android-designer/src/com/intellij/android/designer/model/viewAnimator/RadViewAnimatorLayout.java @@ -17,8 +17,8 @@ package com.intellij.android.designer.model.viewAnimator; import com.intellij.android.designer.designSurface.DropToOperation; import com.intellij.android.designer.designSurface.TreeDropToOperation; -import com.intellij.android.designer.model.RadViewComponent; import com.intellij.android.designer.model.RadViewLayout; +import com.intellij.designer.componentTree.TreeEditOperation; import com.intellij.designer.designSurface.EditOperation; import com.intellij.designer.designSurface.OperationContext; @@ -29,11 +29,13 @@ public class RadViewAnimatorLayout extends RadViewLayout { public EditOperation processChildOperation(OperationContext context) { if ((context.isCreate() || context.isPaste() || context.isAdd() || context.isMove()) && checkChildOperation(context)) { if (context.isTree()) { - return new TreeDropToOperation(myContainer, context); + if (TreeEditOperation.isTarget(myContainer, context)) { + return new TreeDropToOperation(myContainer, context); + } + return null; } - return new DropToOperation((RadViewComponent)myContainer, context); + return new DropToOperation(myContainer, context); } - return null; } diff --git a/plugins/android/common/src/org/jetbrains/android/compiler/tools/AndroidApt.java b/plugins/android/common/src/org/jetbrains/android/compiler/tools/AndroidApt.java index 4457646134b1..c2ee5c64c8b8 100644 --- a/plugins/android/common/src/org/jetbrains/android/compiler/tools/AndroidApt.java +++ b/plugins/android/common/src/org/jetbrains/android/compiler/tools/AndroidApt.java @@ -53,7 +53,7 @@ public final class AndroidApt { @NotNull String outDirOsPath, @NotNull String[] resourceDirsOsPaths, @NotNull String[] libPackages, - boolean isLibrary) throws IOException { + boolean nonConstantFields) throws IOException { final Map> messages = new HashMap>(); messages.put(AndroidCompilerMessageKind.ERROR, new ArrayList()); messages.put(AndroidCompilerMessageKind.INFORMATION, new ArrayList()); @@ -101,7 +101,7 @@ public final class AndroidApt { if (platformToolsRevision < 0 || platformToolsRevision > 7) { Map> map = - doCompile(target, manifestFileOsPath, outDirOsPath, resourceDirsOsPaths, libPackages, null, isLibrary); + doCompile(target, manifestFileOsPath, outDirOsPath, resourceDirsOsPaths, libPackages, null, nonConstantFields); if (map.get(AndroidCompilerMessageKind.ERROR).isEmpty()) { makeFieldsNotFinal(libRJavaFiles); diff --git a/plugins/android/resources/messages/AndroidBundle.properties b/plugins/android/resources/messages/AndroidBundle.properties index 33fd0da9b12c..e9aca3485725 100644 --- a/plugins/android/resources/messages/AndroidBundle.properties +++ b/plugins/android/resources/messages/AndroidBundle.properties @@ -404,4 +404,5 @@ android.launch.hierarchy.viewer.action=Hierarchy Viewer android.launch.draw.9.patch.action=Draw 9 Patch android.facet.settings.include.system.proguard=Include system proguard file file.already.exists.error=File {0} already exists -deployment.target.settings.min.sdk.info.message=Only compatible AVDs are shown. See 'uses-sdk' tag in AndroidManifest.xml \ No newline at end of file +deployment.target.settings.min.sdk.info.message=Only compatible AVDs are shown. See 'uses-sdk' tag in AndroidManifest.xml +android.compilation.warning.circular.app.dependency=Generated fields in {0}.R class in module ''{1}'' won''t be final, because of circular dependency on module ''{2}'' \ No newline at end of file diff --git a/plugins/android/src/org/jetbrains/android/compiler/AndroidAptCompiler.java b/plugins/android/src/org/jetbrains/android/compiler/AndroidAptCompiler.java index 95a880d46831..2e0d4d54db6c 100644 --- a/plugins/android/src/org/jetbrains/android/compiler/AndroidAptCompiler.java +++ b/plugins/android/src/org/jetbrains/android/compiler/AndroidAptCompiler.java @@ -102,7 +102,7 @@ public class AndroidAptCompiler implements SourceGeneratingCompiler { AndroidApt.compile(aptItem.myAndroidTarget, aptItem.myPlatformToolsRevision, aptItem.myManifestFile.getPath(), aptItem.myPackage, outputDirOsPath, aptItem.myResourcesPaths, - aptItem.myLibraryPackages, aptItem.myIsLibrary)); + aptItem.myLibraryPackages, aptItem.myNonConstantFields)); toRefresh = true; AndroidCompileUtil.addMessages(context, messages); if (messages.get(CompilerMessageCategory.ERROR).isEmpty()) { @@ -155,8 +155,8 @@ public class AndroidAptCompiler implements SourceGeneratingCompiler { final String myPackage; final String[] myLibraryPackages; - final boolean myIsLibrary; - + final boolean myNonConstantFields; + final int myPlatformToolsRevision; private final MyValidityState myValidityState; @@ -167,16 +167,16 @@ public class AndroidAptCompiler implements SourceGeneratingCompiler { int platformToolsRevision, @NotNull String aPackage, @NotNull String[] libPackages, - boolean isLibrary) { + boolean nonConstantFields) { myModule = module; myManifestFile = manifestFile; myResourcesPaths = resourcesPaths; myAndroidTarget = target; myPackage = aPackage; myLibraryPackages = libPackages; - myIsLibrary = isLibrary; + myNonConstantFields = nonConstantFields; myPlatformToolsRevision = platformToolsRevision; - myValidityState = new MyValidityState(myModule, Collections.emptySet(), myPlatformToolsRevision); + myValidityState = new MyValidityState(myModule, Collections.emptySet(), myPlatformToolsRevision, myNonConstantFields); } public String getPath() { @@ -218,7 +218,8 @@ public class AndroidAptCompiler implements SourceGeneratingCompiler { AndroidFacet facet = AndroidFacet.getInstance(module); if (facet != null) { AndroidFacetConfiguration configuration = facet.getConfiguration(); - if (!isToCompileModule(module, configuration) || hasBadCircularDependencies(facet)) { + if (!isToCompileModule(module, configuration) || + (facet.getConfiguration().LIBRARY_PROJECT && hasBadCircularDependencies(facet))) { continue; } @@ -261,16 +262,25 @@ public class AndroidAptCompiler implements SourceGeneratingCompiler { } final String[] libPackages = getLibPackages(module, packageName); - items.add(new AptGenerationItem(module, manifestFile, resPaths, target, platformToolsRevision, - packageName, libPackages, facet.getConfiguration().LIBRARY_PROJECT)); + final Module circularDepLibWithSamePackage = AndroidCompileUtil.findCircularDependencyOnLibraryWithSamePackage(facet); + if (circularDepLibWithSamePackage != null) { + myContext.addMessage(CompilerMessageCategory.WARNING, + AndroidBundle.message("android.compilation.warning.circular.app.dependency", + packageName, module.getName(), + circularDepLibWithSamePackage.getName()), null, -1, -1); + } + final boolean generateNonFinalFields = facet.getConfiguration().LIBRARY_PROJECT || circularDepLibWithSamePackage != null; + items.add(new AptGenerationItem(module, manifestFile, resPaths, target, platformToolsRevision, packageName, libPackages, + generateNonFinalFields)); } } return items.toArray(new GenerationItem[items.size()]); } + // support for lib<->lib and app<->lib circular dependencies // see IDEA-79737 for details private static boolean hasBadCircularDependencies(@NotNull AndroidFacet facet) { - final List dependencies = AndroidUtils.getAllAndroidDependencies(facet.getModule(), true); + final List dependencies = AndroidUtils.getAllAndroidDependencies(facet.getModule(), false); final Manifest manifest = facet.getManifest(); if (manifest == null) { @@ -283,18 +293,12 @@ public class AndroidAptCompiler implements SourceGeneratingCompiler { } for (AndroidFacet depFacet : dependencies) { - final Manifest depManifest = depFacet.getManifest(); - - if (depManifest != null) { - if (!aPackage.equals(depManifest.getPackage().getValue())) { - continue; - } - } final List depDependencies = AndroidUtils.getAllAndroidDependencies(depFacet.getModule(), true); if (depDependencies.contains(facet) && dependencies.contains(depFacet) && - depFacet.getModule().getName().compareTo(facet.getModule().getName()) < 0) { + (depFacet.getModule().getName().compareTo(facet.getModule().getName()) < 0 || + !depFacet.getConfiguration().LIBRARY_PROJECT)) { return true; } } @@ -322,11 +326,13 @@ public class AndroidAptCompiler implements SourceGeneratingCompiler { private final String myCustomGenPathR; private final Set myNonExistingFiles; private final int myPlatformToolsRevision; + private final boolean myNonConstantFields; - MyValidityState(@NotNull Module module, @NotNull Set nonExistingFiles, int platformToolsRevision) { + MyValidityState(@NotNull Module module, @NotNull Set nonExistingFiles, int platformToolsRevision, boolean nonConstantFields) { super(module); myNonExistingFiles = nonExistingFiles; myPlatformToolsRevision = platformToolsRevision; + myNonConstantFields = nonConstantFields; AndroidFacet facet = AndroidFacet.getInstance(module); if (facet == null) { myCustomGenPathR = ""; @@ -343,6 +349,7 @@ public class AndroidAptCompiler implements SourceGeneratingCompiler { myNonExistingFiles = Collections.emptySet(); myPlatformToolsRevision = is.readInt(); + myNonConstantFields = is.readBoolean(); } @Override @@ -358,6 +365,9 @@ public class AndroidAptCompiler implements SourceGeneratingCompiler { if (myPlatformToolsRevision != otherState1.myPlatformToolsRevision) { return false; } + if (myNonConstantFields != otherState1.myNonConstantFields) { + return false; + } if (!super.equalsTo(otherState)) { return false; } @@ -369,6 +379,7 @@ public class AndroidAptCompiler implements SourceGeneratingCompiler { super.save(os); os.writeUTF(myCustomGenPathR); os.writeInt(myPlatformToolsRevision); + os.writeBoolean(myNonConstantFields); } } } diff --git a/plugins/android/src/org/jetbrains/android/compiler/AndroidAutogenerator.java b/plugins/android/src/org/jetbrains/android/compiler/AndroidAutogenerator.java index 311e402bb30a..623ade3e0a71 100644 --- a/plugins/android/src/org/jetbrains/android/compiler/AndroidAutogenerator.java +++ b/plugins/android/src/org/jetbrains/android/compiler/AndroidAutogenerator.java @@ -13,7 +13,10 @@ import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.*; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.ReadonlyStatusHandler; +import com.intellij.openapi.vfs.VfsUtilCore; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.search.FilenameIndex; import com.intellij.util.ArrayUtil; import com.intellij.util.containers.HashMap; @@ -159,8 +162,15 @@ public class AndroidAutogenerator { final String manifestFileOsPath = FileUtil.toSystemDependentName(manifestFile.getPath()); + final Module circularDepLibWithSamePackage = AndroidCompileUtil.findCircularDependencyOnLibraryWithSamePackage(facet); + if (circularDepLibWithSamePackage != null) { + context.addMessage(CompilerMessageCategory.WARNING, AndroidBundle.message("android.compilation.warning.circular.app.dependency", + packageName, module.getName(), + circularDepLibWithSamePackage.getName()), null, -1, -1); + } + final boolean generateNonFinalFields = facet.getConfiguration().LIBRARY_PROJECT || circularDepLibWithSamePackage != null; return new AptAutogenerationItem(target, platformToolsRevision, manifestFileOsPath, packageName, sourceRootPath, resPaths, - facet.getConfiguration().LIBRARY_PROJECT, genFilePath2Package); + generateNonFinalFields, genFilePath2Package); } }); @@ -194,7 +204,7 @@ public class AndroidAutogenerator { final Map> messages = AndroidApt.compile(item.myTarget, item.myPlatformToolsRevision, item.myManifestFileOsPath, item.myPackage, - tempOutDir.getPath(), item.myResDirOsPaths, ArrayUtil.EMPTY_STRING_ARRAY, item.myLibrary); + tempOutDir.getPath(), item.myResDirOsPaths, ArrayUtil.EMPTY_STRING_ARRAY, item.myNonConstantFields); if (messages.get(AndroidCompilerMessageKind.ERROR).size() == 0) { for (String genFileRelPath : item.myGenFileRelPath2package.keySet()) { @@ -573,7 +583,7 @@ public class AndroidAutogenerator { final String myPackage; final String myOutputDirOsPath; final String[] myResDirOsPaths; - final boolean myLibrary; + final boolean myNonConstantFields; final Map myGenFileRelPath2package; private AptAutogenerationItem(@NotNull IAndroidTarget target, @@ -582,7 +592,7 @@ public class AndroidAutogenerator { @NotNull String aPackage, @NotNull String outputDirOsPath, @NotNull String[] resDirOsPaths, - boolean library, + boolean nonConstantFields, @NotNull Map genFileRelPath2package) { myTarget = target; myPlatformToolsRevision = platformToolsRevision; @@ -590,7 +600,7 @@ public class AndroidAutogenerator { myPackage = aPackage; myOutputDirOsPath = outputDirOsPath; myResDirOsPaths = resDirOsPaths; - myLibrary = library; + myNonConstantFields = nonConstantFields; myGenFileRelPath2package = genFileRelPath2package; } } diff --git a/plugins/android/src/org/jetbrains/android/compiler/AndroidCompileUtil.java b/plugins/android/src/org/jetbrains/android/compiler/AndroidCompileUtil.java index 8a469cede0f7..c1db61d2f1bc 100644 --- a/plugins/android/src/org/jetbrains/android/compiler/AndroidCompileUtil.java +++ b/plugins/android/src/org/jetbrains/android/compiler/AndroidCompileUtil.java @@ -53,6 +53,7 @@ import com.intellij.psi.search.GlobalSearchScope; import com.intellij.util.ArrayUtil; import com.intellij.util.containers.HashMap; import com.intellij.util.containers.HashSet; +import org.jetbrains.android.dom.manifest.Manifest; import org.jetbrains.android.dom.resources.Attr; import org.jetbrains.android.dom.resources.DeclareStyleable; import org.jetbrains.android.dom.resources.ResourceElement; @@ -875,4 +876,27 @@ public class AndroidCompileUtil { } return null; } + + @Nullable + public static Module findCircularDependencyOnLibraryWithSamePackage(@NotNull AndroidFacet facet) { + final Manifest manifest = facet.getManifest(); + final String aPackage = manifest != null ? manifest.getPackage().getValue() : null; + if (aPackage == null) { + return null; + } + + for (AndroidFacet depFacet : AndroidUtils.getAllAndroidDependencies(facet.getModule(), true)) { + final Manifest depManifest = depFacet.getManifest(); + final String depPackage = depManifest != null ? depManifest.getPackage().getValue() : null; + if (aPackage.equals(depPackage)) { + final List depDependencies = AndroidUtils.getAllAndroidDependencies(depFacet.getModule(), false); + + if (depDependencies.contains(facet)) { + // circular dependency on library with the same package + return depFacet.getModule(); + } + } + } + return null; + } } \ No newline at end of file diff --git a/plugins/android/src/org/jetbrains/android/util/AndroidUtils.java b/plugins/android/src/org/jetbrains/android/util/AndroidUtils.java index a028284ddc1c..cf31d08bcddd 100644 --- a/plugins/android/src/org/jetbrains/android/util/AndroidUtils.java +++ b/plugins/android/src/org/jetbrains/android/util/AndroidUtils.java @@ -664,7 +664,7 @@ public class AndroidUtils { @NotNull public static List getAllAndroidDependencies(@NotNull Module module, boolean androidLibrariesOnly) { final List result = new ArrayList(); - collectAllAndroidDependencies(module, true, result, new HashSet()); + collectAllAndroidDependencies(module, androidLibrariesOnly, result, new HashSet()); return result; } diff --git a/plugins/ant/src/com/intellij/lang/ant/config/impl/AntBeforeRunTask.java b/plugins/ant/src/com/intellij/lang/ant/config/impl/AntBeforeRunTask.java index 136f49bfce28..da5fe11c2bc6 100644 --- a/plugins/ant/src/com/intellij/lang/ant/config/impl/AntBeforeRunTask.java +++ b/plugins/ant/src/com/intellij/lang/ant/config/impl/AntBeforeRunTask.java @@ -26,10 +26,14 @@ import org.jdom.Element; * @author Eugene Zhuravlev * Date: May 18, 2009 */ -public class AntBeforeRunTask extends BeforeRunTask{ +public class AntBeforeRunTask extends BeforeRunTask{ private String myTargetName; private String myAntFileUrl; + public AntBeforeRunTask() { + super(AntBeforeRunTaskProvider.ID); + } + public String getAntFileUrl() { return myAntFileUrl; } diff --git a/plugins/ant/src/com/intellij/lang/ant/config/impl/AntBeforeRunTaskProvider.java b/plugins/ant/src/com/intellij/lang/ant/config/impl/AntBeforeRunTaskProvider.java index 07b5fc38134c..98086d0298a5 100644 --- a/plugins/ant/src/com/intellij/lang/ant/config/impl/AntBeforeRunTaskProvider.java +++ b/plugins/ant/src/com/intellij/lang/ant/config/impl/AntBeforeRunTaskProvider.java @@ -19,6 +19,7 @@ import com.intellij.execution.BeforeRunTaskProvider; import com.intellij.execution.RunManagerEx; import com.intellij.execution.configurations.RunConfiguration; import com.intellij.lang.ant.AntBundle; +import com.intellij.lang.ant.AntIcons; import com.intellij.lang.ant.config.AntBuildFile; import com.intellij.lang.ant.config.AntBuildTarget; import com.intellij.lang.ant.config.AntConfiguration; @@ -29,6 +30,8 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; import org.jetbrains.annotations.Nullable; +import javax.swing.*; + /** * @author Vladislav.Kaznacheev */ @@ -38,13 +41,31 @@ public class AntBeforeRunTaskProvider extends BeforeRunTaskProvider getId() { return ID; } - public String getDescription(final RunConfiguration runConfiguration, AntBeforeRunTask task) { + @Override + public String getName() { + return AntBundle.message("ant.target.before.run.description.empty"); + } + + @Override + public Icon getIcon() { + return AntIcons.ANT_TARGET_ICON; + } + + @Override + public Icon getTaskIcon(AntBeforeRunTask task) { + AntBuildTarget antTarget = findTargetToExecute(task); + return antTarget instanceof MetaTarget ? AntIcons.ANT_META_TARGET_ICON : AntIcons.ANT_TARGET_ICON; + } + + @Override + public String getDescription(AntBeforeRunTask task) { final String targetName = task.getTargetName(); if (targetName == null && !task.isEnabled()) { return AntBundle.message("ant.target.before.run.description.empty"); @@ -52,7 +73,7 @@ public class AntBeforeRunTaskProvider extends BeforeRunTaskProvider"); } - public boolean hasConfigurationButton() { + public boolean isConfigurable() { return true; } @@ -80,6 +101,11 @@ public class AntBeforeRunTaskProvider extends BeforeRunTaskProvider tasks = runManager.getBeforeRunTasks(configuration, AntBeforeRunTaskProvider.ID); + if (!tasks.isEmpty()) { + AntBeforeRunTask task = tasks.get(0);//This is legacy code, we had only one task that time task.setEnabled(true); task.setTargetName(targetName); task.setAntFileUrl(buildFileUrl); @@ -847,8 +848,9 @@ public class AntConfigurationImpl extends AntConfigurationBase implements Persis else { for (ConfigurationFactory factory : type.getConfigurationFactories()) { final RunConfiguration template = runManager.getConfigurationTemplate(factory).getConfiguration(); - final AntBeforeRunTask task = runManager.getBeforeRunTask(template, AntBeforeRunTaskProvider.ID); - if (task != null) { + final List tasks = runManager.getBeforeRunTasks(template, AntBeforeRunTaskProvider.ID); + if (!tasks.isEmpty()) { + AntBeforeRunTask task = tasks.get(0);//This is legacy code, we had only one task that time task.setEnabled(true); task.setTargetName(targetName); task.setAntFileUrl(buildFileUrl); diff --git a/plugins/ant/src/com/intellij/lang/ant/refactoring/AntTargetRenameListenerProvider.java b/plugins/ant/src/com/intellij/lang/ant/refactoring/AntTargetRenameListenerProvider.java index 69f318afa946..c1a290f43902 100644 --- a/plugins/ant/src/com/intellij/lang/ant/refactoring/AntTargetRenameListenerProvider.java +++ b/plugins/ant/src/com/intellij/lang/ant/refactoring/AntTargetRenameListenerProvider.java @@ -17,10 +17,10 @@ package com.intellij.lang.ant.refactoring; import com.intellij.execution.BeforeRunTaskProvider; import com.intellij.lang.ant.config.AntConfiguration; +import com.intellij.lang.ant.config.impl.AntBeforeRunTask; import com.intellij.lang.ant.config.impl.AntBeforeRunTaskProvider; import com.intellij.lang.ant.config.impl.AntConfigurationImpl; import com.intellij.lang.ant.psi.AntTarget; -import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.util.Comparing; import com.intellij.psi.PsiElement; import com.intellij.refactoring.listeners.RefactoringElementListener; @@ -45,11 +45,10 @@ public class AntTargetRenameListenerProvider implements RefactoringElementListen final AntConfiguration configuration = AntConfiguration.getInstance(newElement.getProject()); ((AntConfigurationImpl)configuration).handleTargetRename(oldName, newName); - for (BeforeRunTaskProvider provider : Extensions.getExtensions(AntBeforeRunTaskProvider.EXTENSION_POINT_NAME, newElement.getProject())) { - if (AntBeforeRunTaskProvider.ID.equals(provider.getId())) { - ((AntBeforeRunTaskProvider)provider).handleTargetRename(oldName, newName); - break; - } + BeforeRunTaskProvider provider = + BeforeRunTaskProvider.getProvider(newElement.getProject(), AntBeforeRunTaskProvider.ID); + if (provider instanceof AntBeforeRunTaskProvider) { + ((AntBeforeRunTaskProvider)provider).handleTargetRename(oldName, newName); } } } diff --git a/plugins/git4idea/src/git4idea/Notificator.java b/plugins/git4idea/src/git4idea/Notificator.java index b1b11f4ab7f7..9943af7cf3e4 100644 --- a/plugins/git4idea/src/git4idea/Notificator.java +++ b/plugins/git4idea/src/git4idea/Notificator.java @@ -68,11 +68,23 @@ public class Notificator { } public void notifyError(@NotNull String title, @NotNull String message) { - notify(GitVcs.IMPORTANT_ERROR_NOTIFICATION, title, message, NotificationType.ERROR, null); + notifyError(title, message, null); + } + + public void notifyError(@NotNull String title, @NotNull String message, @Nullable NotificationListener listener) { + notify(GitVcs.IMPORTANT_ERROR_NOTIFICATION, title, message, NotificationType.ERROR, listener); } public void notifySuccess(@NotNull String title, @NotNull String message) { notify(GitVcs.NOTIFICATION_GROUP_ID, title, message, NotificationType.INFORMATION, null); } + public void notifyWeakWarning(@NotNull String title, @NotNull String message, @Nullable NotificationListener listener) { + notify(GitVcs.MINOR_NOTIFICATION, title, message, NotificationType.WARNING, listener); + } + + public void notifyStrongWarning(@NotNull String title, @NotNull String content, @Nullable NotificationListener listener) { + notify(GitVcs.IMPORTANT_ERROR_NOTIFICATION, title, content, NotificationType.WARNING, listener); + } + } diff --git a/plugins/git4idea/src/git4idea/PlatformFacade.java b/plugins/git4idea/src/git4idea/PlatformFacade.java index bad5f7f3aa00..79aa28977fd8 100644 --- a/plugins/git4idea/src/git4idea/PlatformFacade.java +++ b/plugins/git4idea/src/git4idea/PlatformFacade.java @@ -15,15 +15,17 @@ */ package git4idea; +import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.util.Computable; import com.intellij.openapi.vcs.AbstractVcs; +import com.intellij.openapi.vcs.AbstractVcsHelper; import com.intellij.openapi.vcs.ProjectLevelVcsManager; import com.intellij.openapi.vcs.changes.ChangeListManager; import com.intellij.openapi.vfs.LocalFileSystem; -import git4idea.config.GitVcsSettings; +import git4idea.repo.GitRepositoryManager; import org.jetbrains.annotations.NotNull; /** @@ -65,12 +67,17 @@ public interface PlatformFacade { void runReadAction(@NotNull Runnable runnable); - /** - * @return the instance of {@link git4idea.config.GitVcsSettings} - */ - GitVcsSettings getGitWorkspaceSettings(@NotNull Project project); + void runWriteAction(@NotNull Runnable runnable); + + void invokeAndWait(@NotNull Runnable runnable, @NotNull ModalityState modalityState); ChangeListManager getChangeListManager(@NotNull Project project); LocalFileSystem getLocalFileSystem(); + + @NotNull + AbstractVcsHelper getVcsHelper(@NotNull Project project); + + @NotNull + GitRepositoryManager getRepositoryManager(@NotNull Project project); } diff --git a/plugins/git4idea/src/git4idea/PlatformFacadeImpl.java b/plugins/git4idea/src/git4idea/PlatformFacadeImpl.java index 9664d0dfff06..01b29d3d4797 100644 --- a/plugins/git4idea/src/git4idea/PlatformFacadeImpl.java +++ b/plugins/git4idea/src/git4idea/PlatformFacadeImpl.java @@ -16,15 +16,18 @@ package git4idea; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.util.Computable; import com.intellij.openapi.vcs.AbstractVcs; +import com.intellij.openapi.vcs.AbstractVcsHelper; import com.intellij.openapi.vcs.ProjectLevelVcsManager; import com.intellij.openapi.vcs.changes.ChangeListManager; import com.intellij.openapi.vfs.LocalFileSystem; -import git4idea.config.GitVcsSettings; +import git4idea.repo.GitRepositoryManager; import org.jetbrains.annotations.NotNull; /** @@ -66,8 +69,13 @@ public class PlatformFacadeImpl implements PlatformFacade { } @Override - public GitVcsSettings getGitWorkspaceSettings(@NotNull Project project) { - return GitVcsSettings.getInstance(project); + public void runWriteAction(@NotNull Runnable runnable) { + ApplicationManager.getApplication().runWriteAction(runnable); + } + + @Override + public void invokeAndWait(@NotNull Runnable runnable, @NotNull ModalityState modalityState) { + ApplicationManager.getApplication().invokeAndWait(runnable, modalityState); } @Override @@ -80,6 +88,18 @@ public class PlatformFacadeImpl implements PlatformFacade { return LocalFileSystem.getInstance(); } + @NotNull + @Override + public AbstractVcsHelper getVcsHelper(@NotNull Project project) { + return AbstractVcsHelper.getInstance(project); + } + + @NotNull + @Override + public GitRepositoryManager getRepositoryManager(@NotNull Project project) { + return ServiceManager.getService(project, GitRepositoryManager.class); + } + @NotNull @Override public AbstractVcs getVcs(@NotNull Project project) { diff --git a/plugins/git4idea/src/git4idea/branch/GitBranchOperation.java b/plugins/git4idea/src/git4idea/branch/GitBranchOperation.java index f06edf61b637..144ae4320ce5 100644 --- a/plugins/git4idea/src/git4idea/branch/GitBranchOperation.java +++ b/plugins/git4idea/src/git4idea/branch/GitBranchOperation.java @@ -297,7 +297,7 @@ abstract class GitBranchOperation { setMergeDescription("The following files have unresolved conflicts. You need to resolve them before " + getOperationName() + "."). setErrorNotificationTitle("Unresolved files remain."); - new GitConflictResolver(myProject, GitUtil.getRoots(getRepositories()), params).merge(); + new GitConflictResolver(myProject, myGit, ServiceManager.getService(PlatformFacade.class), GitUtil.getRoots(getRepositories()), params).merge(); } } }); @@ -333,7 +333,8 @@ abstract class GitBranchOperation { } private void showUntrackedFilesNotification(@NotNull Collection untrackedFiles) { - UntrackedFilesNotifier.notifyUntrackedFilesOverwrittenBy(myProject, untrackedFiles, getOperationName()); + UntrackedFilesNotifier.notifyUntrackedFilesOverwrittenBy(myProject, ServiceManager.getService(myProject, PlatformFacade.class), + untrackedFiles, getOperationName(), null); } private void showUntrackedFilesDialogWithRollback(@NotNull Collection untrackedFiles) { diff --git a/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java b/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java index 770fdfe6a94f..139ac72b22aa 100644 --- a/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java +++ b/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java @@ -201,7 +201,8 @@ class GitCheckoutOperation extends GitBranchOperation { private boolean smartCheckout(@NotNull final List repositories, @NotNull final String reference, @Nullable final String newBranch, @NotNull ProgressIndicator indicator) { final AtomicBoolean result = new AtomicBoolean(); - GitPreservingProcess preservingProcess = new GitPreservingProcess(myProject, repositories, "checkout", reference, indicator, new Runnable() { + GitPreservingProcess preservingProcess = new GitPreservingProcess(myProject, myGit, repositories, "checkout", reference, indicator, + new Runnable() { @Override public void run() { result.set(checkoutOrNotify(repositories, reference, newBranch, false)); diff --git a/plugins/git4idea/src/git4idea/branch/GitMergeOperation.java b/plugins/git4idea/src/git4idea/branch/GitMergeOperation.java index 468a7f85d17f..afc13e1eb852 100644 --- a/plugins/git4idea/src/git4idea/branch/GitMergeOperation.java +++ b/plugins/git4idea/src/git4idea/branch/GitMergeOperation.java @@ -209,7 +209,7 @@ class GitMergeOperation extends GitBranchOperation { private boolean doSmartMerge(@NotNull final Collection repositories) { final AtomicBoolean success = new AtomicBoolean(); - myPreservingProcess = new GitPreservingProcess(myProject, repositories, "merge", myBranchToMerge, getIndicator(), + myPreservingProcess = new GitPreservingProcess(myProject, myGit, repositories, "merge", myBranchToMerge, getIndicator(), new Runnable() { @Override public void run() { @@ -303,7 +303,8 @@ class GitMergeOperation extends GitBranchOperation { private GitCompoundResult smartRollback(@NotNull final Collection repositories) { LOG.info("Starting smart rollback..."); final GitCompoundResult result = new GitCompoundResult(myProject); - GitPreservingProcess preservingProcess = new GitPreservingProcess(myProject, repositories, "merge", myBranchToMerge, getIndicator(), + GitPreservingProcess preservingProcess = new GitPreservingProcess(myProject, myGit, repositories, "merge", myBranchToMerge, + getIndicator(), new Runnable() { @Override public void run() { for (GitRepository repository : repositories) { @@ -361,7 +362,7 @@ class GitMergeOperation extends GitBranchOperation { private class MyMergeConflictResolver extends GitMergeCommittingConflictResolver { public MyMergeConflictResolver() { - super(GitMergeOperation.this.myProject, new GitMerger(GitMergeOperation.this.myProject), + super(GitMergeOperation.this.myProject, myGit, new GitMerger(GitMergeOperation.this.myProject), GitUtil.getRoots(GitMergeOperation.this.myConflictedRepositories.keySet()), new Params(), true); } diff --git a/plugins/git4idea/src/git4idea/changes/GitChangeUtils.java b/plugins/git4idea/src/git4idea/changes/GitChangeUtils.java index 789ff3a680d7..731e9be9e63b 100644 --- a/plugins/git4idea/src/git4idea/changes/GitChangeUtils.java +++ b/plugins/git4idea/src/git4idea/changes/GitChangeUtils.java @@ -23,7 +23,6 @@ import com.intellij.openapi.vcs.FileStatus; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.changes.Change; import com.intellij.openapi.vcs.changes.ContentRevision; -import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.ArrayUtil; import git4idea.GitContentRevision; @@ -32,9 +31,9 @@ import git4idea.GitUtil; import git4idea.commands.GitCommand; import git4idea.commands.GitHandler; import git4idea.commands.GitSimpleHandler; -import git4idea.util.StringScanner; import git4idea.history.browser.SHAHash; import git4idea.history.wholeTree.AbstractHash; +import git4idea.util.StringScanner; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -57,49 +56,6 @@ public class GitChangeUtils { private GitChangeUtils() { } - /** - * Parse changes from lines - * - * @param project the context project - * @param root the git root - * @return a set of unmerged files - * @throws VcsException if the input format does not matches expected format - */ - public static List unmergedFiles(Project project, VirtualFile root) throws VcsException { - HashSet unmerged = new HashSet(); - String rootPath = root.getPath(); - GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.LS_FILES); - h.setNoSSH(true); - h.setSilent(true); - h.addParameters("--unmerged"); - LocalFileSystem lfs = LocalFileSystem.getInstance(); - for (StringScanner s = new StringScanner(h.run()); s.hasMoreData();) { - if (s.isEol()) { - s.nextLine(); - continue; - } - s.boundedToken('\t'); - final String relative = s.line(); - String path = rootPath + "/" + GitUtil.unescapePath(relative); - VirtualFile file = lfs.refreshAndFindFileByPath(path); - if (file != null) { - // the file name is in the delete- or rename- conflict, so it is shown in the list of unmerged files, - // but the file itself doesn't exist. In that case we just ignore the file. - file.refresh(false, false); - unmerged.add(file); - } - } - if (unmerged.size() == 0) { - return Collections.emptyList(); - } - else { - ArrayList rc = new ArrayList(unmerged.size()); - rc.addAll(unmerged); - Collections.sort(rc, GitUtil.VIRTUAL_FILE_COMPARATOR); - return rc; - } - } - /** * Parse changes from lines * diff --git a/plugins/git4idea/src/git4idea/commands/Git.java b/plugins/git4idea/src/git4idea/commands/Git.java index a4dfbb727e37..beb4ffaf0b54 100644 --- a/plugins/git4idea/src/git4idea/commands/Git.java +++ b/plugins/git4idea/src/git4idea/commands/Git.java @@ -33,42 +33,38 @@ import java.util.Set; */ public interface Git { + @NotNull GitCommandResult init(@NotNull Project project, @NotNull VirtualFile root, @NotNull GitLineHandlerListener... listeners); @NotNull - Set untrackedFiles(@NotNull Project project, - @NotNull VirtualFile root, + Set untrackedFiles(@NotNull Project project, @NotNull VirtualFile root, @Nullable Collection files) throws VcsException; // relativePaths are guaranteed to fit into command line length limitations. @NotNull - Collection untrackedFilesNoChunk(@NotNull Project project, - @NotNull VirtualFile root, - @Nullable List relativePaths) - throws VcsException; + Collection untrackedFilesNoChunk(@NotNull Project project, @NotNull VirtualFile root, + @Nullable List relativePaths) throws VcsException; @NotNull GitCommandResult clone(@NotNull Project project, @NotNull File parentDirectory, @NotNull String url, @NotNull String clonedDirectoryName); @NotNull - GitCommandResult merge(@NotNull GitRepository repository, @NotNull String branchToMerge, - @NotNull GitLineHandlerListener... listeners); + GitCommandResult merge(@NotNull GitRepository repository, @NotNull String branchToMerge, @NotNull GitLineHandlerListener... listeners); - GitCommandResult checkout(@NotNull GitRepository repository, - @NotNull String reference, - @Nullable String newBranch, - boolean force, + @NotNull + GitCommandResult checkout(@NotNull GitRepository repository, @NotNull String reference, @Nullable String newBranch, boolean force, @NotNull GitLineHandlerListener... listeners); + @NotNull GitCommandResult checkoutNewBranch(@NotNull GitRepository repository, @NotNull String branchName, @Nullable GitLineHandlerListener listener); + @NotNull GitCommandResult createNewTag(@NotNull GitRepository repository, @NotNull String tagName, - @Nullable GitLineHandlerListener listener, String reference); + @Nullable GitLineHandlerListener listener, @NotNull String reference); - GitCommandResult branchDelete(@NotNull GitRepository repository, - @NotNull String branchName, - boolean force, + @NotNull + GitCommandResult branchDelete(@NotNull GitRepository repository, @NotNull String branchName, boolean force, @NotNull GitLineHandlerListener... listeners); @NotNull @@ -83,6 +79,7 @@ public interface Git { @NotNull GitCommandResult resetMerge(@NotNull GitRepository repository, @Nullable String revision); + @NotNull GitCommandResult tip(@NotNull GitRepository repository, @NotNull String branchName); @NotNull @@ -92,4 +89,12 @@ public interface Git { @NotNull GitCommandResult push(@NotNull GitRepository repository, @NotNull GitPushSpec pushSpec, @NotNull GitLineHandlerListener... listeners); + + @NotNull + GitCommandResult cherryPick(@NotNull GitRepository repository, @NotNull String hash, boolean autoCommit, + @NotNull GitLineHandlerListener... listeners); + + @NotNull + GitCommandResult getUnmergedFiles(@NotNull GitRepository repository); + } diff --git a/plugins/git4idea/src/git4idea/commands/GitCommandResult.java b/plugins/git4idea/src/git4idea/commands/GitCommandResult.java index 147b4ce6cfe9..0c53924bd630 100644 --- a/plugins/git4idea/src/git4idea/commands/GitCommandResult.java +++ b/plugins/git4idea/src/git4idea/commands/GitCommandResult.java @@ -73,4 +73,9 @@ public class GitCommandResult { public String getErrorOutputAsJoinedString() { return StringUtil.join(myErrorOutput, "\n"); } + + @NotNull + public String getOutputAsJoinedString() { + return StringUtil.join(myOutput, "\n"); + } } diff --git a/plugins/git4idea/src/git4idea/commands/GitImpl.java b/plugins/git4idea/src/git4idea/commands/GitImpl.java index c70436c9f58f..9f9eeae22688 100644 --- a/plugins/git4idea/src/git4idea/commands/GitImpl.java +++ b/plugins/git4idea/src/git4idea/commands/GitImpl.java @@ -51,6 +51,7 @@ public class GitImpl implements Git { /** * Calls 'git init' on the specified directory. */ + @NotNull @Override public GitCommandResult init(@NotNull Project project, @NotNull VirtualFile root, @NotNull GitLineHandlerListener... listeners) { GitLineHandler h = new GitLineHandler(project, root, GitCommand.INIT); @@ -153,6 +154,7 @@ public class GitImpl implements Git { * {@code git checkout <reference>}
* {@code git checkout -b <newBranch> <reference>} */ + @NotNull @Override public GitCommandResult checkout(@NotNull GitRepository repository, @NotNull String reference, @@ -179,6 +181,7 @@ public class GitImpl implements Git { /** * {@code git checkout -b <branchName>} */ + @NotNull @Override public GitCommandResult checkoutNewBranch(@NotNull GitRepository repository, @NotNull String branchName, @Nullable GitLineHandlerListener listener) { @@ -192,9 +195,10 @@ public class GitImpl implements Git { return run(h); } + @NotNull @Override public GitCommandResult createNewTag(@NotNull GitRepository repository, @NotNull String tagName, - @Nullable GitLineHandlerListener listener, String reference) { + @Nullable GitLineHandlerListener listener, @NotNull String reference) { final GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.TAG); h.setSilent(false); h.addParameters(tagName); @@ -210,6 +214,7 @@ public class GitImpl implements Git { /** * {@code git branch -d } or {@code git branch -D } */ + @NotNull @Override public GitCommandResult branchDelete(@NotNull GitRepository repository, @NotNull String branchName, @@ -272,6 +277,7 @@ public class GitImpl implements Git { * Returns the last (tip) commit on the given branch.
* {@code git rev-list -1 } */ + @NotNull @Override public GitCommandResult tip(@NotNull GitRepository repository, @NotNull String branchName) { final GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.REV_LIST); @@ -287,9 +293,7 @@ public class GitImpl implements Git { final GitLineHandlerPasswordRequestAware h = new GitLineHandlerPasswordRequestAware(repository.getProject(), repository.getRoot(), GitCommand.PUSH); h.setSilent(false); - for (GitLineHandlerListener listener : listeners) { - h.addLineListener(listener); - } + addListeners(h, listeners); h.addParameters(remote); h.addParameters(spec); return run(h, true); @@ -305,6 +309,36 @@ public class GitImpl implements Git { return push(repository, remote.getName(), pushSpec.getSource().getName() + ":" + destination, listeners); } + @Override + @NotNull + public GitCommandResult cherryPick(@NotNull GitRepository repository, @NotNull String hash, boolean autoCommit, + @NotNull GitLineHandlerListener... listeners) { + final GitLineHandler handler = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.CHERRY_PICK); + handler.addParameters("-x"); + if (!autoCommit) { + handler.addParameters("-n"); + } + handler.addParameters(hash); + addListeners(handler, listeners); + handler.setSilent(false); + return run(handler); + } + + @NotNull + @Override + public GitCommandResult getUnmergedFiles(@NotNull GitRepository repository) { + GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.LS_FILES); + h.addParameters("--unmerged"); + h.setSilent(true); + return run(h); + } + + private static void addListeners(@NotNull GitLineHandler handler, @NotNull GitLineHandlerListener... listeners) { + for (GitLineHandlerListener listener : listeners) { + handler.addLineListener(listener); + } + } + private GitCommandResult run(@NotNull GitLineHandler handler) { return run(handler, false); } diff --git a/plugins/git4idea/src/git4idea/commands/GitSimpleEventDetector.java b/plugins/git4idea/src/git4idea/commands/GitSimpleEventDetector.java index 216db2ad3aa8..b057ad1aa2ca 100644 --- a/plugins/git4idea/src/git4idea/commands/GitSimpleEventDetector.java +++ b/plugins/git4idea/src/git4idea/commands/GitSimpleEventDetector.java @@ -26,6 +26,8 @@ public class GitSimpleEventDetector implements GitLineHandlerListener { private boolean myHappened; public enum Event { + CHERRY_PICK_CONFLICT("fter resolving the conflicts"), // a is uppercase in 1.7.0.5 and lowercase in 1.7.9.2 + LOCAL_CHANGES_OVERWRITTEN_BY_CHERRY_PICK("would be overwritten by merge"), UNMERGED_PREVENTING_CHECKOUT("you need to resolve your current index first"), UNMERGED_PREVENTING_MERGE("is not possible because you have unmerged files"), BRANCH_NOT_FULLY_MERGED("is not fully merged"), diff --git a/plugins/git4idea/src/git4idea/config/GitVcsPanel.form b/plugins/git4idea/src/git4idea/config/GitVcsPanel.form index 701079981a94..3feefe8e52be 100644 --- a/plugins/git4idea/src/git4idea/config/GitVcsPanel.form +++ b/plugins/git4idea/src/git4idea/config/GitVcsPanel.form @@ -123,7 +123,7 @@ - + @@ -133,7 +133,7 @@ - + @@ -142,13 +142,22 @@ - + + + + + + + + + + diff --git a/plugins/git4idea/src/git4idea/config/GitVcsPanel.java b/plugins/git4idea/src/git4idea/config/GitVcsPanel.java index 6d869ee727af..6eedc1507ba3 100644 --- a/plugins/git4idea/src/git4idea/config/GitVcsPanel.java +++ b/plugins/git4idea/src/git4idea/config/GitVcsPanel.java @@ -54,6 +54,7 @@ public class GitVcsPanel { private JComboBox myConvertTextFilesComboBox; // The conversion policy private JCheckBox myAutoUpdateIfPushRejected; private JBCheckBox mySyncBranchControl; + private JCheckBox myAutoCommitOnCherryPick; public GitVcsPanel(@NotNull Project project) { myVcs = GitVcs.getInstance(project); @@ -124,6 +125,7 @@ public class GitVcsPanel { myConvertTextFilesComboBox.setSelectedItem(crlfPolicyItem(settings)); myAutoUpdateIfPushRejected.setSelected(settings.autoUpdateIfPushRejected()); mySyncBranchControl.setSelected(settings.getSyncSetting() == GitBranchSyncSetting.SYNC); + myAutoCommitOnCherryPick.setSelected(settings.isAutoCommitOnCherryPick()); } /** @@ -161,7 +163,8 @@ public class GitVcsPanel { (settings.isIdeaSsh() != IDEA_SSH.equals(mySSHExecutableComboBox.getSelectedItem())) || !crlfPolicyItem(settings).equals(myConvertTextFilesComboBox.getSelectedItem()) || !settings.autoUpdateIfPushRejected() == myAutoUpdateIfPushRejected.isSelected() || - ((settings.getSyncSetting() == GitBranchSyncSetting.SYNC) != mySyncBranchControl.isSelected()); + ((settings.getSyncSetting() == GitBranchSyncSetting.SYNC) != mySyncBranchControl.isSelected() || + settings.isAutoCommitOnCherryPick() == myAutoCommitOnCherryPick.isSelected()); } /** @@ -189,5 +192,6 @@ public class GitVcsPanel { } settings.setLineSeparatorsConversion(conversionPolicy); settings.setSyncSetting(mySyncBranchControl.isSelected() ? GitBranchSyncSetting.SYNC : GitBranchSyncSetting.DONT); + settings.setAutoCommitOnCherryPick(myAutoCommitOnCherryPick.isSelected()); } } diff --git a/plugins/git4idea/src/git4idea/config/GitVcsSettings.java b/plugins/git4idea/src/git4idea/config/GitVcsSettings.java index 61ccb83bc04a..80099983ee20 100644 --- a/plugins/git4idea/src/git4idea/config/GitVcsSettings.java +++ b/plugins/git4idea/src/git4idea/config/GitVcsSettings.java @@ -74,6 +74,7 @@ public class GitVcsSettings implements PersistentStateComponent RECENT_BRANCH_BY_REPOSITORY = new HashMap(); public String RECENT_COMMON_BRANCH = null; + public boolean AUTO_COMMIT_ON_CHERRY_PICK = false; } public GitVcsSettings(GitVcsApplicationSettings appSettings) { @@ -190,4 +191,12 @@ public class GitVcsSettings implements PersistentStateComponent myCommits; - @NotNull private final CheckinEnvironment myCheckinEnvironment; - private final LowLevelAccess myAccess; + @NotNull private final Project myProject; + @NotNull private final Git myGit; + @NotNull private final PlatformFacade myPlatformFacade; + @NotNull private final ChangeListManager myChangeListManager; + private final boolean myAutoCommit; - private final List myExceptions; - private final List myWarnings; - private boolean myConflictsExist; - private final ChangeListManager myChangeListManager; - - private final List myCherryPickedData; - - public CherryPicker(GitVcs vcs, final List commits, LowLevelAccess access) { - myVcs = vcs; - myCommits = commits; - myAccess = access; - - myChangeListManager = PeriodicalTasksCloser.getInstance().safeGetComponent(myVcs.getProject(), ChangeListManager.class); - CheckinEnvironment ce = myVcs.getCheckinEnvironment(); - LOG.assertTrue(ce != null); - myCheckinEnvironment = ce; - - myExceptions = new ArrayList(); - myWarnings = new ArrayList(); - myCherryPickedData = new ArrayList(); + public CherryPicker(@NotNull Project project, @NotNull Git git, @NotNull PlatformFacade platformFacade, boolean autoCommit) { + myProject = project; + myGit = git; + myPlatformFacade = platformFacade; + myAutoCommit = autoCommit; + myChangeListManager = myPlatformFacade.getChangeListManager(myProject); } - public void execute() { - for (GitCommit commit : myCommits) { - cherryPickStep(commit); + public void cherryPick(@NotNull Map> commitsInRoots) { + List successfulCommits = new ArrayList(); + for (Map.Entry> entry : commitsInRoots.entrySet()) { + if (!cherryPick(entry.getKey(), entry.getValue(), successfulCommits)) { + return; + } } - - // remove those that are in newer lists - checkListsForSamePaths(); - - refreshChangedFiles(); - findAndProcessChangedForVcs(); - - showResults(); + notifySuccess(successfulCommits); } - private void refreshChangedFiles() { - for (FilePath file : getAllChangedFiles()) { - VirtualFile vf = LocalFileSystem.getInstance().refreshAndFindFileByPath(file.getPath()); + // return true to continue with other roots, false to break execution + private boolean cherryPick(@NotNull GitRepository repository, @NotNull List commits, + @NotNull List successfulCommits) { + for (GitCommit commit : commits) { + GitSimpleEventDetector conflictDetector = new GitSimpleEventDetector(CHERRY_PICK_CONFLICT); + GitSimpleEventDetector localChangesOverwrittenDetector = new GitSimpleEventDetector(LOCAL_CHANGES_OVERWRITTEN_BY_CHERRY_PICK); + GitMessageWithFilesDetector untrackedFilesDetector = new GitMessageWithFilesDetector(UNTRACKED_FILES_OVERWRITTEN_BY, + repository.getRoot()); + GitCommandResult result = myGit.cherryPick(repository, commit.getHash().getValue(), myAutoCommit, + conflictDetector, localChangesOverwrittenDetector, untrackedFilesDetector); + if (result.success()) { + if (myAutoCommit) { + successfulCommits.add(commit); + } + else { + boolean committed = updateChangeListManagerShowCommitDialogAndRemoveChangeListOnSuccess(repository, commit, successfulCommits); + if (!committed) { + notifyCommitCancelled(commit, successfulCommits); + return false; + } + } + } + else if (conflictDetector.hasHappened()) { + boolean mergeCompleted = new CherryPickConflictResolver(myProject, myGit, myPlatformFacade, repository.getRoot(), + commit.getShortHash().getString(), commit.getAuthor(), + commit.getSubject()).merge(); + + if (mergeCompleted) { + boolean committed = updateChangeListManagerShowCommitDialogAndRemoveChangeListOnSuccess(repository, commit, successfulCommits); + if (!committed) { + notifyCommitCancelled(commit, successfulCommits); + return false; + } + } + else { + updateChangeListManager(commit); + notifyConflictWarning(repository, commit, successfulCommits); + return false; + } + } + else if (untrackedFilesDetector.wasMessageDetected()) { + String description = commitDetails(commit) + + "
Some untracked working tree files would be overwritten by cherry-pick.
" + + "Please move, remove or add them before you can cherry-pick. View them"; + description += getSuccessfulCommitDetailsIfAny(successfulCommits); + + UntrackedFilesNotifier.notifyUntrackedFilesOverwrittenBy(myProject, myPlatformFacade, untrackedFilesDetector.getFiles(), + "cherry-pick", description); + return false; + } + else if (localChangesOverwrittenDetector.hasHappened()) { + notifyError("Your local changes would be overwritten by cherry-pick.
Commit your changes or stash them to proceed.", + commit, successfulCommits); + return false; + } + else { + notifyError(result.getErrorOutputAsHtmlString(), commit, successfulCommits); + return false; + } + } + return true; + } + + private boolean updateChangeListManagerShowCommitDialogAndRemoveChangeListOnSuccess(@NotNull GitRepository repository, + @NotNull GitCommit commit, + @NotNull List successfulCommits) { + CherryPickData data = updateChangeListManager(commit); + boolean committed = showCommitDialog(repository, commit, data.myChangeList, data.myCommitMessage); + if (committed) { + removeChangeList(data); + successfulCommits.add(commit); + return true; + } + return false; + } + + private void removeChangeList(CherryPickData list) { + myChangeListManager.setDefaultChangeList(list.myPreviouslyDefaultChangeList); + if (!myChangeListManager.getDefaultChangeList().equals(list.myChangeList)) { + myChangeListManager.removeChangeList(list.myChangeList); + } + } + + private void notifyConflictWarning(@NotNull GitRepository repository, @NotNull GitCommit commit, + @NotNull List successfulCommits) { + NotificationListener resolveLinkListener = new ResolveLinkListener(myProject, myGit, myPlatformFacade, repository.getRoot(), + commit.getShortHash().getString(), commit.getAuthor(), + commit.getSubject()); + String description = commitDetails(commit) + + "
Unresolved conflicts remain in the working tree. Resolve them."; + description += getSuccessfulCommitDetailsIfAny(successfulCommits); + myPlatformFacade.getNotificator(myProject).notifyStrongWarning("Cherry-picked with conflicts", description, resolveLinkListener); + } + + private void notifyCommitCancelled(@NotNull GitCommit commit, @NotNull List successfulCommits) { + String description = commitDetails(commit); + description += getSuccessfulCommitDetailsIfAny(successfulCommits); + myPlatformFacade.getNotificator(myProject).notifyWeakWarning("Cherry-pick cancelled", description, null); + } + + private CherryPickData updateChangeListManager(@NotNull final GitCommit commit) { + final Collection paths = ChangesUtil.getPaths(commit.getChanges()); + refreshChangedFiles(paths); + final String commitMessage = createCommitMessage(commit, paths); + LocalChangeList previouslyDefaultChangeList = myChangeListManager.getDefaultChangeList(); + LocalChangeList changeList = createChangeListAfterUpdate(commit.getChanges(), paths, commitMessage); + return new CherryPickData(changeList, commitMessage, previouslyDefaultChangeList); + } + + @NotNull + private LocalChangeList createChangeListAfterUpdate(@NotNull final List changes, @NotNull final Collection paths, + @NotNull final String commitMessage) { + final AtomicReference changeList = new AtomicReference(); + myPlatformFacade.invokeAndWait(new Runnable() { + @Override + public void run() { + myChangeListManager.invokeAfterUpdate(new Runnable() { + public void run() { + changeList.set(createChangeList(changes, commitMessage)); + } + }, InvokeAfterUpdateMode.SYNCHRONOUS_NOT_CANCELLABLE, "", + new Consumer() { + public void consume(VcsDirtyScopeManager vcsDirtyScopeManager) { + vcsDirtyScopeManager.filePathsDirty(paths, null); + } + }, ModalityState.NON_MODAL + ); + } + }, ModalityState.NON_MODAL); + + + return changeList.get(); + } + + @NotNull + private String createCommitMessage(@NotNull GitCommit commit, @NotNull Collection paths) { + CheckinEnvironment ce = myPlatformFacade.getVcs(myProject).getCheckinEnvironment(); + String message = ce == null ? null : ce.getDefaultMessageFor(ArrayUtil.toObjectArray(paths, FilePath.class)); + message = message == null ? commit.getDescription() + "\n(cherry-picked from " + commit.getShortHash().getString() + ")" : message; + return message; + } + + private boolean showCommitDialog(@NotNull final GitRepository repository, @NotNull final GitCommit commit, + @NotNull final LocalChangeList changeList, @NotNull final String commitMessage) { + final AtomicBoolean commitSucceeded = new AtomicBoolean(); + myPlatformFacade.invokeAndWait(new Runnable() { + @Override + public void run() { + cancelCherryPick(repository); + List changes = commit.getChanges(); + CherryPickCommitExecutor executor = new CherryPickCommitExecutor(myProject, myPlatformFacade, changes, commitMessage); + boolean commitNotCancelled = myPlatformFacade.getVcsHelper(myProject).commitChanges(changes, changeList, commitMessage, executor); + commitSucceeded.set(commitNotCancelled && !executor.hasCommitFailed()); + } + }, ModalityState.NON_MODAL); + return commitSucceeded.get(); + } + + /** + * We control the cherry-pick workflow ourselves + we want to use partial commits ('git commit --only'), which is prohibited during + * cherry-pick, i.e. until the CHERRY_PICK_HEAD exists. + */ + private void cancelCherryPick(@NotNull GitRepository repository) { + if (myAutoCommit) { + removeCherryPickHead(repository); + } + } + + private void removeCherryPickHead(@NotNull GitRepository repository) { + File cherryPickHeadFile = new File(repository.getGitDir().getPath(), "CHERRY_PICK_HEAD"); + final VirtualFile cherryPickHead = myPlatformFacade.getLocalFileSystem().refreshAndFindFileByIoFile(cherryPickHeadFile); + + if (cherryPickHead != null && cherryPickHead.exists()) { + myPlatformFacade.runWriteAction(new Runnable() { + @Override + public void run() { + try { + cherryPickHead.delete(this); + } + catch (IOException e) { + // if CHERRY_PICK_HEAD is not deleted, the partial commit will fail, and the user will be notified anyway. + // So here we just log the fact. It is happens relatively often, maybe some additional solution will follow. + LOG.error(e); + } + } + }); + } + else { + LOG.info("Cancel cherry-pick in " + repository.getPresentableUrl() + ": no CHERRY_PICK_HEAD found"); + } + } + + private void notifyError(@NotNull String content, @NotNull GitCommit failedCommit, @NotNull List successfulCommits) { + String description = commitDetails(failedCommit) + "
" + content; + description += getSuccessfulCommitDetailsIfAny(successfulCommits); + myPlatformFacade.getNotificator(myProject).notifyError("Cherry-pick failed", description); + } + + @NotNull + private static String getSuccessfulCommitDetailsIfAny(@NotNull List successfulCommits) { + String description = ""; + if (!successfulCommits.isEmpty()) { + description += "
However cherry-pick succeeded for the following " + pluralize("commit", successfulCommits.size()) + ":
"; + description += getCommitsDetails(successfulCommits); + } + return description; + } + + private void notifySuccess(@NotNull List successfulCommits) { + String description = getCommitsDetails(successfulCommits); + myPlatformFacade.getNotificator(myProject).notifySuccess("Cherry-pick successful", description); + } + + @NotNull + private static String getCommitsDetails(@NotNull List successfulCommits) { + String description = ""; + for (GitCommit commit : successfulCommits) { + description += commitDetails(commit) + "
"; + } + return description.substring(0, description.length() - "
".length()); + } + + @NotNull + private static String commitDetails(@NotNull GitCommit commit) { + return commit.getShortHash().toString() + " \"" + commit.getSubject() + "\""; + } + + private void refreshChangedFiles(@NotNull Collection filePaths) { + for (FilePath file : filePaths) { + VirtualFile vf = myPlatformFacade.getLocalFileSystem().refreshAndFindFileByPath(file.getPath()); if (vf != null) { vf.refresh(false, false); } @@ -90,190 +322,230 @@ public class CherryPicker { } @NotNull - private Collection getAllChangedFiles() { - Collection files = new ArrayList(); - for (CherryPickedData data : myCherryPickedData) { - files.addAll(data.getFiles()); + private LocalChangeList createChangeList(@NotNull List changes, @NotNull String commitMessage) { + if (!changes.isEmpty()) { + final LocalChangeList changeList = myChangeListManager.addChangeList(commitMessage, commitMessage); + myChangeListManager.moveChangesTo(changeList, changes.toArray(new Change[changes.size()])); + myChangeListManager.setDefaultChangeList(changeList); + return changeList; } - return files; + return myChangeListManager.getDefaultChangeList(); } - private void findAndProcessChangedForVcs() { - myChangeListManager.invokeAfterUpdate(new Runnable() { - public void run() { - moveToCorrectLists(); - } - }, InvokeAfterUpdateMode.SILENT, "", new Consumer() { - public void consume(VcsDirtyScopeManager vcsDirtyScopeManager) { - vcsDirtyScopeManager.filePathsDirty(getAllChangedFiles(), null); - } - }, ModalityState.NON_MODAL); - } + private static class CherryPickData { + private final LocalChangeList myChangeList; + private final String myCommitMessage; + private final LocalChangeList myPreviouslyDefaultChangeList; - private void showResults() { - final Project project = myVcs.getProject(); - if (myExceptions.isEmpty() && !myConflictsExist) { - showOverChangesView(project, "Successful cherry-pick into working tree, please commit changes", MessageType.INFO); - } else { - if (myExceptions.isEmpty()) { - showOverChangesView(project, "Unresolved conflicts while cherry-picking. Resolve conflicts, then commit changes", - MessageType.WARNING); - } else { - showOverChangesView(project, "Errors in cherry-pick", MessageType.ERROR); - } - } - if ((! myExceptions.isEmpty()) || (! myWarnings.isEmpty())) { - myExceptions.addAll(myWarnings); - AbstractVcsHelper.getInstance(project).showErrors(myExceptions, "Cherry-pick problems"); + private CherryPickData(LocalChangeList list, String message, LocalChangeList previouslyDefaultChangeList) { + myChangeList = list; + myCommitMessage = message; + myPreviouslyDefaultChangeList = previouslyDefaultChangeList; } } - private void moveToCorrectLists() { - for (CherryPickedData pickedData : myCherryPickedData) { - final Collection filePaths = pickedData.getFiles(); - final String message = pickedData.getCommitMessage(); + private static class CherryPickConflictResolver extends GitConflictResolver { - if (filePaths.isEmpty()) continue; - - final List changes = pathsToChanges(filePaths); - pickedData.setChanges(changes); - if (!changes.isEmpty()) { - final LocalChangeList cl = myChangeListManager.addChangeList(message, null); - pickedData.setChangeList(cl); - myChangeListManager.moveChangesTo(cl, changes.toArray(new Change[changes.size()])); - } + public CherryPickConflictResolver(@NotNull Project project, @NotNull Git git, @NotNull PlatformFacade facade, @NotNull VirtualFile root, + @NotNull String commitHash, @NotNull String commitAuthor, @NotNull String commitMessage) { + super(project, git, facade, Collections.singleton(root), makeParams(commitHash, commitAuthor, commitMessage)); } + + private static Params makeParams(String commitHash, String commitAuthor, String commitMessage) { + Params params = new Params(); + params.setErrorNotificationTitle("Cherry-picked with conflicts"); + params.setMergeDialogCustomizer(new CherryPickMergeDialogCustomizer(commitHash, commitAuthor, commitMessage)); + return params; + } + + @Override + protected void notifyUnresolvedRemain() { + // we show a [possibly] compound notification after cherry-picking all commits. + } + } - @NotNull - private List pathsToChanges(@NotNull Collection filePaths) { - final List changes = new ArrayList(filePaths.size()); - for (FilePath filePath : filePaths) { - changes.add(myChangeListManager.getChange(filePath)); + private static class ResolveLinkListener implements NotificationListener { + @NotNull private final Project myProject; + @NotNull private final Git myGit; + @NotNull private final PlatformFacade myFacade; + @NotNull private final VirtualFile myRoot; + @NotNull private final String myHash; + @NotNull private final String myAuthor; + @NotNull private final String myMessage; + + public ResolveLinkListener(@NotNull Project project, @NotNull Git git, @NotNull PlatformFacade facade, @NotNull VirtualFile root, + @NotNull String commitHash, @NotNull String commitAuthor, @NotNull String commitMessage) { + + myProject = project; + myGit = git; + myFacade = facade; + myRoot = root; + myHash = commitHash; + myAuthor = commitAuthor; + myMessage = commitMessage; } - return changes; - } - private void checkListsForSamePaths() { - List myMessagesInOrder = new ArrayList(myCherryPickedData.size()); - Map> myFilesToMove = new HashMap>(myCherryPickedData.size()); - for (CherryPickedData data : myCherryPickedData) { - myMessagesInOrder.add(data.getCommitMessage()); - myFilesToMove.put(data.getCommitMessage(), data.getFiles()); - } - final GroupOfListsProcessor listsProcessor = new GroupOfListsProcessor(); - listsProcessor.process(myMessagesInOrder, myFilesToMove); - final Set lostSet = listsProcessor.getHaveLostSomething(); - markFilesMovesToNewerLists(myWarnings, lostSet, myFilesToMove); - } - - private void cherryPickStep(@NotNull GitCommit commit) { - try { - if (!myAccess.cherryPick(commit)) { - myConflictsExist = true; - } - } - catch (VcsException e) { - myExceptions.add(e); - } - final List changes = commit.getChanges(); - - final Collection paths = ChangesUtil.getPaths(changes); - String message = myCheckinEnvironment.getDefaultMessageFor(paths.toArray(new FilePath[paths.size()])); - message = (message == null) ? commit.getDescription() + " (cherry picked from commit " + commit.getShortHash() + ")" : message; - - myCherryPickedData.add(new CherryPickedData(message, paths)); - } - - private static void markFilesMovesToNewerLists(List exceptions, Set lostSet, - Map> filesToMove) { - if (! lostSet.isEmpty()) { - final StringBuilder sb = new StringBuilder("Some changes are moved from following list(s) to other:"); - boolean first = true; - for (String s : lostSet) { - if (filesToMove.get(s).isEmpty()) { - final VcsException exc = - new VcsException("Changelist not created since all files moved to other cherry-pick(s): '" + s + "'"); - exc.setIsWarning(true); - exceptions.add(exc); - continue; + @Override + public void hyperlinkUpdate(@NotNull Notification notification, + @NotNull HyperlinkEvent event) { + if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { + if (event.getDescription().equals("resolve")) { + new CherryPickConflictResolver(myProject, myGit, myFacade, myRoot, myHash, myAuthor, myMessage).mergeNoProceed(); } - sb.append(s); - if (! first) { - sb.append(", "); - } - first = false; - } - if (! first) { - final VcsException exc = new VcsException(sb.toString()); - exc.setIsWarning(true); - exceptions.add(exc); } } } - private static class GroupOfListsProcessor { - private final Set myHaveLostSomething; + private static class CherryPickMergeDialogCustomizer extends MergeDialogCustomizer { - private GroupOfListsProcessor() { - myHaveLostSomething = new HashSet(); + private String myCommitHash; + private String myCommitAuthor; + private String myCommitMessage; + + public CherryPickMergeDialogCustomizer(String commitHash, String commitAuthor, String commitMessage) { + myCommitHash = commitHash; + myCommitAuthor = commitAuthor; + myCommitMessage = commitMessage; } - public void process(final List messagesInOrder, final Map> filesToMove) { - // remove those that are in newer lists - for (int i = 1; i < messagesInOrder.size(); i++) { - final String message = messagesInOrder.get(i); - final Collection currentFiles = filesToMove.get(message); + @Override + public String getMultipleFileMergeDescription(Collection files) { + return "Conflicts during cherry-picking commit " + myCommitHash + " made by " + myCommitAuthor + "
" + + "\"" + myCommitMessage + "\""; + } - for (int j = 0; j < i; j++) { - final String previous = messagesInOrder.get(j); - final boolean somethingChanged = filesToMove.get(previous).removeAll(currentFiles); - if (somethingChanged) { - myHaveLostSomething.add(previous); + @Override + public String getLeftPanelTitle(VirtualFile file) { + return "Local changes"; + } + + @Override + public String getRightPanelTitle(VirtualFile file, VcsRevisionNumber lastRevisionNumber) { + return "Changes from cherry-pick " + myCommitHash + ""; + } + } + + /* + Commit procedure is overridden by the executor with its own CommitSession. + The reason of that is the asynchronous nature of the CommitHelper: it returns, we continue cherry-picking and occasionally pick + the next commit in the queue, and only then Git is called for commit. Thus it commits two cherry-picks at once, which is wrong. + + Here we call GitCheckinEnvironment manually + */ + private static class CherryPickCommitExecutor implements CommitExecutor { + + @NotNull private final Project myProject; + @NotNull private final PlatformFacade myPlatformFacade; + @NotNull private final List myChanges; + @NotNull private final String myCommitMessage; + private boolean myCommitFailed; + + CherryPickCommitExecutor(@NotNull Project project, @NotNull PlatformFacade platformFacade, + @NotNull List changes, @NotNull String commitMessage) { + myProject = project; + myPlatformFacade = platformFacade; + myChanges = changes; + myCommitMessage = commitMessage; + } + + @Nls + @Override + public String getActionText() { + return "Commit"; + } + + @NotNull + @Override + public CommitSession createCommitSession() { + return new CherryPickCommitSession(); + } + + public boolean hasCommitFailed() { + return myCommitFailed; + } + + private class CherryPickCommitSession implements CommitSession { + @Override + public JComponent getAdditionalConfigurationUI() { + return null; + } + + @Override + public JComponent getAdditionalConfigurationUI(Collection changes, String commitMessage) { + return null; + } + + @Override + public boolean canExecute(Collection changes, String commitMessage) { + return true; + } + + @Override + public void execute(Collection changes, String commitMessage) { + final Collection committingDocs = markCommittingDocs(); + try { + CheckinEnvironment ce = myPlatformFacade.getVcs(myProject).getCheckinEnvironment(); + if (ce != null) { + try { + List exceptions = ce.commit(myChanges, myCommitMessage); + VcsDirtyScopeManager.getInstance(myProject).filePathsDirty(ChangesUtil.getPaths(myChanges), null); + if (exceptions != null && !exceptions.isEmpty()) { + VcsException exception = exceptions.get(0); + handleError(exception); + } + } + catch (Throwable e) { + LOG.error(e); + handleError(e); + } } } + finally { + unmarkCommittingDocs(committingDocs); + } } - } - public Set getHaveLostSomething() { - return myHaveLostSomething; - } - } + private void handleError(Throwable exception) { + myCommitFailed = true; + final String errorMessage = exception.getMessage(); + WaitForProgressToShow.runOrInvokeLaterAboveProgress(new Runnable() { + public void run() { + Messages.showErrorDialog(myProject, errorMessage, "Commit Failed"); + } + }, null, myProject); + } - private static class CherryPickedData { + @Override + public void executionCanceled() { + } - private final String myCommitMessage; - private final Collection myFiles; - private LocalChangeList myChangeList; - private Collection myChanges; + @Override + public String getHelpId() { + return null; + } - private CherryPickedData(@NotNull String message, @NotNull Collection files) { - myCommitMessage = message; - myFiles = files; - } + private void unmarkCommittingDocs(final Collection committingDocs) { + myPlatformFacade.runReadAction(new Runnable() { + @Override + public void run() { + CommitHelper.unmarkCommittingDocuments(committingDocs); + } + }); + } - public Collection getChanges() { - return myChanges; - } + @NotNull + private Collection markCommittingDocs() { + final Collection committingDocs = new ArrayList(); + myPlatformFacade.runReadAction(new Runnable() { + @Override + public void run() { + committingDocs.addAll(CommitHelper.markCommittingDocuments(myProject, myChanges)); + } + }); + return committingDocs; + } - public LocalChangeList getChangeList() { - return myChangeList; - } - - public String getCommitMessage() { - return myCommitMessage; - } - - public Collection getFiles() { - return myFiles; - } - - public void setChanges(List changes) { - myChanges = changes; - } - - public void setChangeList(LocalChangeList changeList) { - myChangeList = changeList; } } diff --git a/plugins/git4idea/src/git4idea/history/browser/LowLevelAccess.java b/plugins/git4idea/src/git4idea/history/browser/LowLevelAccess.java index 2771f3046361..2aca5e7395b7 100644 --- a/plugins/git4idea/src/git4idea/history/browser/LowLevelAccess.java +++ b/plugins/git4idea/src/git4idea/history/browser/LowLevelAccess.java @@ -56,14 +56,6 @@ public interface LowLevelAccess { void loadAllTags(final Collection sink) throws VcsException; - /** - * Cherry-picks the specified commit. - * Doesn't autocommit - instead puts the changes into a separate changelist. - * In the case of merge conflict provides the Conflict Resolver dialog. - * @return true if all conflicts were resolved or there were no merge conflicts; false if unresolved files remain. - * @throws VcsException - */ - boolean cherryPick(GitCommit hash) throws VcsException; void loadHashesWithParents(final @NotNull Collection startingPoints, @NotNull final Collection filters, final AsynchConsumer consumer, Getter isCanceled, int useMaxCnt, final boolean topoOrder) throws VcsException; diff --git a/plugins/git4idea/src/git4idea/history/browser/LowLevelAccessImpl.java b/plugins/git4idea/src/git4idea/history/browser/LowLevelAccessImpl.java index 64f8707fb354..8f8ea572719f 100644 --- a/plugins/git4idea/src/git4idea/history/browser/LowLevelAccessImpl.java +++ b/plugins/git4idea/src/git4idea/history/browser/LowLevelAccessImpl.java @@ -18,6 +18,7 @@ package git4idea.history.browser; import com.intellij.notification.Notification; import com.intellij.notification.NotificationListener; import com.intellij.notification.NotificationType; +import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Getter; @@ -29,11 +30,9 @@ import com.intellij.openapi.vcs.merge.MergeDialogCustomizer; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.ArrayUtil; import com.intellij.util.AsynchConsumer; -import git4idea.GitBranch; -import git4idea.GitTag; -import git4idea.GitUtil; -import git4idea.GitVcs; +import git4idea.*; import git4idea.branch.GitBranchesCollection; +import git4idea.commands.Git; import git4idea.commands.GitCommand; import git4idea.commands.GitLineHandler; import git4idea.commands.GitLineHandlerAdapter; @@ -43,6 +42,7 @@ import git4idea.history.wholeTree.AbstractHash; import git4idea.history.wholeTree.CommitHashPlusParents; import git4idea.merge.GitConflictResolver; import git4idea.repo.GitRepository; +import git4idea.repo.GitRepositoryImpl; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -121,7 +121,7 @@ public class LowLevelAccessImpl implements LowLevelAccess { if (! child.exists()) { throw new VcsException("No git repository in " + myRoot.getPath()); } - repository = GitRepository.getLightInstance(myRoot, myProject, myProject); + repository = GitRepositoryImpl.getLightInstance(myRoot, myProject, myProject); repository.getBranches(); } GitBranchesCollection branches = repository.getBranches(); @@ -212,102 +212,4 @@ public class LowLevelAccessImpl implements LowLevelAccess { GitTag.listAsStrings(myProject, myRoot, sink, null); } - public boolean cherryPick(GitCommit commit) throws VcsException { - final GitLineHandler handler = new GitLineHandler(myProject, myRoot, GitCommand.CHERRY_PICK); - handler.addParameters("-x", "-n", commit.getHash().getValue()); - handler.endOptions(); - handler.setNoSSH(true); - - final AtomicBoolean conflict = new AtomicBoolean(); - - handler.addLineListener(new GitLineHandlerAdapter() { - public void onLineAvailable(String line, Key outputType) { - if (line.toLowerCase().contains("after resolving the conflicts")) { - conflict.set(true); - } - } - }); - handler.runInCurrentThread(null); - - if (conflict.get()) { - return new CherryPickConflictResolver(myProject, myRoot, commit.getShortHash().getString(), commit.getAuthor(), commit.getSubject()).merge(); - } else { - final List errors = handler.errors(); - if (!errors.isEmpty()) { - throw errors.get(0); - } else { // no conflicts, no errors - return true; - } - } - } - - private static class CherryPickConflictResolver extends GitConflictResolver { - - private VirtualFile myRoot; - private String myCommitHash; - private String myCommitAuthor; - private String myCommitMessage; - - public CherryPickConflictResolver(Project project, VirtualFile root, String commitHash, String commitAuthor, String commitMessage) { - super(project, Collections.singleton(root), makeParams(commitHash, commitAuthor, commitMessage)); - myRoot = root; - myCommitHash = commitHash; - myCommitAuthor = commitAuthor; - myCommitMessage = commitMessage; - } - - private static Params makeParams(String commitHash, String commitAuthor, String commitMessage) { - Params params = new Params(); - params.setErrorNotificationTitle("Cherry-picked with conflicts"); - params.setMergeDialogCustomizer(new CherryPickMergeDialogCustomizer(commitHash, commitAuthor, commitMessage)); - return params; - } - - @Override - protected void notifyUnresolvedRemain() { - GitVcs.IMPORTANT_ERROR_NOTIFICATION.createNotification("Conflicts were not resolved during cherry-pick", - "Cherry-pick is not complete, you have unresolved merges in your working tree
" + - "
Resolve conflicts.", - NotificationType.WARNING, new NotificationListener() { - @Override - public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) { - if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { - if (event.getDescription().equals("resolve")) { - new CherryPickConflictResolver(myProject, myRoot, myCommitHash, myCommitAuthor, myCommitMessage).mergeNoProceed(); - } - } - } - }).notify(myProject); - } - } - - private static class CherryPickMergeDialogCustomizer extends MergeDialogCustomizer { - - private String myCommitHash; - private String myCommitAuthor; - private String myCommitMessage; - - public CherryPickMergeDialogCustomizer(String commitHash, String commitAuthor, String commitMessage) { - myCommitHash = commitHash; - myCommitAuthor = commitAuthor; - myCommitMessage = commitMessage; - } - - @Override - public String getMultipleFileMergeDescription(Collection files) { - return "Conflicts during cherry-picking commit " + myCommitHash + " made by " + myCommitAuthor + "
" + - "\"" + myCommitMessage + "\""; - } - - @Override - public String getLeftPanelTitle(VirtualFile file) { - return "Local changes"; - } - - @Override - public String getRightPanelTitle(VirtualFile file, VcsRevisionNumber lastRevisionNumber) { - return "Changes from cherry-pick " + myCommitHash + ""; - } - } - } diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/GitLogUI.java b/plugins/git4idea/src/git4idea/history/wholeTree/GitLogUI.java index 33387f731093..2d978e3afba7 100644 --- a/plugins/git4idea/src/git4idea/history/wholeTree/GitLogUI.java +++ b/plugins/git4idea/src/git4idea/history/wholeTree/GitLogUI.java @@ -17,12 +17,14 @@ import com.intellij.ide.actions.ContextHelpAction; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.actionSystem.impl.SimpleDataContext; -import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diff.impl.CaptionIcon; import com.intellij.openapi.diff.impl.patch.formove.FilePathComparator; import com.intellij.openapi.ide.CopyPasteManager; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; @@ -58,10 +60,14 @@ import com.intellij.util.ui.UIUtil; import com.intellij.vcsUtil.VcsUtil; import git4idea.GitUtil; import git4idea.GitVcs; +import git4idea.PlatformFacade; import git4idea.branch.GitBranchOperationsProcessor; import git4idea.changes.GitChangeUtils; +import git4idea.commands.Git; +import git4idea.config.GitVcsSettings; import git4idea.history.browser.*; import git4idea.repo.GitRepository; +import git4idea.repo.GitRepositoryManager; import git4idea.ui.branch.GitBranchUiUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -1608,17 +1614,14 @@ public class GitLogUI implements Disposable { myIdsInProgress.add(commit.getShortHash()); } - final Application application = ApplicationManager.getApplication(); - application.executeOnPooledThread(new Runnable() { - public void run() { - for (VirtualFile file : commits.keySet()) { - final List part = (List)commits.get(file); - // earliest first!!! - Collections.reverse(part); - new CherryPicker(GitVcs.getInstance(myProject), part, new LowLevelAccessImpl(myProject, file)).execute(); - } + new Task.Backgroundable(myProject, "Cherry-picking", false) { + public void run(@NotNull ProgressIndicator indicator) { + boolean autoCommit = GitVcsSettings.getInstance(myProject).isAutoCommitOnCherryPick(); + Map> commitsInRoots = prepareCommitsForCherryPick(commits); + new CherryPicker(myProject, ServiceManager.getService(Git.class), ServiceManager.getService(PlatformFacade.class), autoCommit) + .cherryPick(commitsInRoots); - application.invokeLater(new Runnable() { + ApplicationManager.getApplication().invokeLater(new Runnable() { public void run() { for (GitCommit commit : commits.values()) { myIdsInProgress.remove(commit.getShortHash()); @@ -1626,7 +1629,23 @@ public class GitLogUI implements Disposable { } }); } - }); + }.queue(); + } + + private Map> prepareCommitsForCherryPick(MultiMap commits) { + Map> commitsInRoots = new HashMap>(); + GitRepositoryManager repositoryManager = ServiceManager.getService(myProject, GitRepositoryManager.class); + for (Map.Entry> entry : commits.entrySet()) { + List sortedCommits = new ArrayList(entry.getValue()); + // earliest first!!! + Collections.reverse(sortedCommits); + GitRepository repository = repositoryManager.getRepositoryForRoot(entry.getKey()); + if (repository == null) { + continue; + } + commitsInRoots.put(repository, sortedCommits); + } + return commitsInRoots; } // newest first diff --git a/plugins/git4idea/src/git4idea/merge/GitConflictResolver.java b/plugins/git4idea/src/git4idea/merge/GitConflictResolver.java index 351bb674b5cf..e546dfe61b38 100644 --- a/plugins/git4idea/src/git4idea/merge/GitConflictResolver.java +++ b/plugins/git4idea/src/git4idea/merge/GitConflictResolver.java @@ -21,18 +21,26 @@ import com.intellij.notification.NotificationType; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.AbstractVcsHelper; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.merge.MergeDialogCustomizer; import com.intellij.openapi.vcs.merge.MergeProvider; +import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.ui.UIUtil; +import git4idea.GitUtil; import git4idea.GitVcs; +import git4idea.PlatformFacade; +import git4idea.commands.Git; +import git4idea.commands.GitCommandResult; +import git4idea.repo.GitRepository; +import git4idea.repo.GitRepositoryManager; +import git4idea.util.StringScanner; import org.jetbrains.annotations.NotNull; import javax.swing.event.HyperlinkEvent; -import java.util.ArrayList; -import java.util.Collection; +import java.util.*; /** * @@ -43,12 +51,14 @@ public class GitConflictResolver { private static final Logger LOG = Logger.getInstance(GitConflictResolver.class); - protected final Project myProject; + @NotNull protected final Project myProject; + @NotNull private final Git myGit; + @NotNull private final PlatformFacade myPlatformFacade; private final Collection myRoots; private final Params myParams; + @NotNull private final GitRepositoryManager myRepositoryManager; private final AbstractVcsHelper myVcsHelper; - private final GitVcs myVcs; /** * Customizing parameters - mostly String notification texts, etc. @@ -94,13 +104,15 @@ public class GitConflictResolver { } - public GitConflictResolver(@NotNull Project project, @NotNull Collection roots, @NotNull Params params) { + public GitConflictResolver(@NotNull Project project, @NotNull Git git, @NotNull PlatformFacade platformFacade, + @NotNull Collection roots, @NotNull Params params) { myProject = project; + myGit = git; + myPlatformFacade = platformFacade; myRoots = roots; myParams = params; - - myVcsHelper = AbstractVcsHelper.getInstance(project); - myVcs = GitVcs.getInstance(project); + myRepositoryManager = myPlatformFacade.getRepositoryManager(myProject); + myVcsHelper = myPlatformFacade.getVcsHelper(project); } /** @@ -141,8 +153,12 @@ public class GitConflictResolver { protected boolean proceedAfterAllMerged() throws VcsException { return true; } - - protected final boolean mergeNoProceed() { + + /** + * Invoke the merge dialog, but execute nothing after merge is completed. + * @return true if all changes were merged, false if unresolved merges remain. + */ + public final boolean mergeNoProceed() { return merge(true); } @@ -165,12 +181,12 @@ public class GitConflictResolver { } private void notifyWarning(String title, String content) { - GitVcs.IMPORTANT_ERROR_NOTIFICATION.createNotification(title, content, NotificationType.WARNING, new ResolveNotificationListener()).notify(myProject); + myPlatformFacade.getNotificator(myProject).notifyStrongWarning(title, content, new ResolveNotificationListener()); } private boolean merge(boolean mergeDialogInvokedFromNotification) { try { - final Collection initiallyUnmergedFiles = GitMergeUtil.getUnmergedFiles(myProject, myRoots); + final Collection initiallyUnmergedFiles = getUnmergedFiles(myRoots); if (initiallyUnmergedFiles.isEmpty()) { LOG.info("merge: no unmerged files"); return mergeDialogInvokedFromNotification ? true : proceedIfNothingToMerge(); @@ -178,7 +194,7 @@ public class GitConflictResolver { else { showMergeDialog(initiallyUnmergedFiles); - final Collection unmergedFilesAfterResolve = GitMergeUtil.getUnmergedFiles(myProject, myRoots); + final Collection unmergedFilesAfterResolve = getUnmergedFiles(myRoots); if (unmergedFilesAfterResolve.isEmpty()) { LOG.info("merge no more unmerged files"); return mergeDialogInvokedFromNotification ? true : proceedAfterAllMerged(); @@ -201,7 +217,8 @@ public class GitConflictResolver { private void showMergeDialog(final Collection initiallyUnmergedFiles) { UIUtil.invokeAndWaitIfNeeded(new Runnable() { @Override public void run() { - final MergeProvider mergeProvider = myParams.reverse ? myVcs.getReverseMergeProvider() : myVcs.getMergeProvider(); + final MergeProvider mergeProvider = myParams.reverse ? + new GitMergeProvider(myProject, true) : new GitMergeProvider(myProject, false); myVcsHelper.showMergeDialog(new ArrayList(initiallyUnmergedFiles), mergeProvider, myParams.myMergeDialogCustomizer); } }); @@ -238,4 +255,77 @@ public class GitConflictResolver { } } + /** + * @return unmerged files in the given Git roots, all in a single collection. + * @see #getUnmergedFiles(com.intellij.openapi.vfs.VirtualFile) + */ + private Collection getUnmergedFiles(@NotNull Collection roots) throws VcsException { + final Collection unmergedFiles = new HashSet(); + for (VirtualFile root : roots) { + unmergedFiles.addAll(getUnmergedFiles(root)); + } + return unmergedFiles; + } + + /** + * @return unmerged files in the given Git root. + * @see #getUnmergedFiles(java.util.Collection + */ + private Collection getUnmergedFiles(@NotNull VirtualFile root) throws VcsException { + return unmergedFiles(root); + } + + /** + * Parse changes from lines + * + * + * @param root the git root + * @return a set of unmerged files + * @throws com.intellij.openapi.vcs.VcsException if the input format does not matches expected format + */ + private List unmergedFiles(VirtualFile root) throws VcsException { + HashSet unmerged = new HashSet(); + String rootPath = root.getPath(); + + GitRepository repository = myRepositoryManager.getRepositoryForRoot(root); + if (repository == null) { + LOG.error("Repository not found for root " + root); + return Collections.emptyList(); + } + + GitCommandResult result = myGit.getUnmergedFiles(repository); + if (!result.success()) { + throw new VcsException(result.getErrorOutputAsJoinedString()); + } + + String output = StringUtil.join(result.getOutput(), "\n"); + + LocalFileSystem lfs = myPlatformFacade.getLocalFileSystem(); + for (StringScanner s = new StringScanner(output); s.hasMoreData();) { + if (s.isEol()) { + s.nextLine(); + continue; + } + s.boundedToken('\t'); + final String relative = s.line(); + String path = rootPath + "/" + GitUtil.unescapePath(relative); + VirtualFile file = lfs.refreshAndFindFileByPath(path); + if (file != null) { + // the file name is in the delete- or rename- conflict, so it is shown in the list of unmerged files, + // but the file itself doesn't exist. In that case we just ignore the file. + file.refresh(false, false); + unmerged.add(file); + } + } + if (unmerged.size() == 0) { + return Collections.emptyList(); + } + else { + ArrayList rc = new ArrayList(unmerged.size()); + rc.addAll(unmerged); + Collections.sort(rc, GitUtil.VIRTUAL_FILE_COMPARATOR); + return rc; + } + } + } diff --git a/plugins/git4idea/src/git4idea/merge/GitMergeCommittingConflictResolver.java b/plugins/git4idea/src/git4idea/merge/GitMergeCommittingConflictResolver.java index 4c48ab6dd0dd..d72c7b796618 100644 --- a/plugins/git4idea/src/git4idea/merge/GitMergeCommittingConflictResolver.java +++ b/plugins/git4idea/src/git4idea/merge/GitMergeCommittingConflictResolver.java @@ -15,9 +15,13 @@ */ package git4idea.merge; +import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vfs.VirtualFile; +import git4idea.PlatformFacade; +import git4idea.commands.Git; +import org.jetbrains.annotations.NotNull; import java.util.Collection; @@ -31,9 +35,9 @@ public class GitMergeCommittingConflictResolver extends GitConflictResolver { private final boolean myRefreshAfterCommit; private final GitMerger myMerger; - public GitMergeCommittingConflictResolver(Project project, GitMerger merger, Collection mergingRoots, Params params, - boolean refreshAfterCommit) { - super(project, mergingRoots, params); + public GitMergeCommittingConflictResolver(Project project, @NotNull Git git, GitMerger merger, Collection mergingRoots, + Params params, boolean refreshAfterCommit) { + super(project, git, ServiceManager.getService(PlatformFacade.class), mergingRoots, params); myMerger = merger; myMergingRoots = mergingRoots; myRefreshAfterCommit = refreshAfterCommit; diff --git a/plugins/git4idea/src/git4idea/merge/GitMergeUtil.java b/plugins/git4idea/src/git4idea/merge/GitMergeUtil.java index fd922b294bb4..bcdbe705db5f 100644 --- a/plugins/git4idea/src/git4idea/merge/GitMergeUtil.java +++ b/plugins/git4idea/src/git4idea/merge/GitMergeUtil.java @@ -34,7 +34,6 @@ import com.intellij.openapi.vfs.VirtualFile; import git4idea.GitRevisionNumber; import git4idea.GitVcs; import git4idea.actions.GitRepositoryAction; -import git4idea.changes.GitChangeUtils; import git4idea.i18n.GitBundle; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -44,7 +43,6 @@ import javax.swing.*; import java.io.File; import java.util.ArrayList; import java.util.Collection; -import java.util.HashSet; import java.util.List; /** @@ -189,25 +187,4 @@ public class GitMergeUtil { public static boolean isMergeInProgress(@NotNull VirtualFile root) { return getMergeHead(root) != null; } - - /** - * @return unmerged files in the given Git roots, all in a single collection. - * @see #getUnmergedFiles(com.intellij.openapi.project.Project, com.intellij.openapi.vfs.VirtualFile) - */ - public static Collection getUnmergedFiles(@NotNull Project project, @NotNull Collection roots) throws VcsException { - final Collection unmergedFiles = new HashSet(); - for (VirtualFile root : roots) { - unmergedFiles.addAll(getUnmergedFiles(project, root)); - } - return unmergedFiles; - } - - /** - * @return unmerged files in the given Git root. - * @see #getUnmergedFiles(com.intellij.openapi.project.Project, java.util.Collection) - */ - public static Collection getUnmergedFiles(@NotNull Project project, @NotNull VirtualFile root) throws VcsException { - return GitChangeUtils.unmergedFiles(project, root); - } - } diff --git a/plugins/git4idea/src/git4idea/rebase/GitRebaser.java b/plugins/git4idea/src/git4idea/rebase/GitRebaser.java index 1ee4146473ca..24e2d9ed7c5b 100644 --- a/plugins/git4idea/src/git4idea/rebase/GitRebaser.java +++ b/plugins/git4idea/src/git4idea/rebase/GitRebaser.java @@ -15,6 +15,7 @@ */ package git4idea.rebase; +import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; @@ -24,6 +25,7 @@ import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vfs.VirtualFile; import git4idea.GitUtil; import git4idea.GitVcs; +import git4idea.PlatformFacade; import git4idea.commands.*; import git4idea.merge.GitConflictResolver; import git4idea.util.GitUIUtil; @@ -47,10 +49,12 @@ public class GitRebaser { private GitVcs myVcs; private List mySkippedCommits; private static final Logger LOG = Logger.getInstance(GitRebaser.class); + @NotNull private final Git myGit; private final @Nullable ProgressIndicator myProgressIndicator; - public GitRebaser(Project project, ProgressIndicator progressIndicator) { + public GitRebaser(Project project, @NotNull Git git, ProgressIndicator progressIndicator) { myProject = project; + myGit = git; myProgressIndicator = progressIndicator; myVcs = GitVcs.getInstance(project); mySkippedCommits = new ArrayList(); @@ -172,7 +176,7 @@ public class GitRebaser { private boolean handleRebaseFailure(final VirtualFile root, final GitLineHandler h, GitRebaseProblemDetector rebaseConflictDetector) { if (rebaseConflictDetector.isMergeConflict()) { LOG.info("handleRebaseFailure merge conflict"); - return new GitConflictResolver(myProject, Collections.singleton(root), makeParamsForRebaseConflict()) { + return new GitConflictResolver(myProject, myGit, ServiceManager.getService(PlatformFacade.class), Collections.singleton(root), makeParamsForRebaseConflict()) { @Override protected boolean proceedIfNothingToMerge() { return continueRebase(root, "--continue"); } diff --git a/plugins/git4idea/src/git4idea/repo/GitRepository.java b/plugins/git4idea/src/git4idea/repo/GitRepository.java index 12f571199f28..5fb09189c8f2 100644 --- a/plugins/git4idea/src/git4idea/repo/GitRepository.java +++ b/plugins/git4idea/src/git4idea/repo/GitRepository.java @@ -15,23 +15,14 @@ */ package git4idea.repo; -import com.intellij.openapi.Disposable; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.util.Consumer; -import com.intellij.util.concurrency.QueueProcessor; -import com.intellij.util.messages.MessageBus; -import com.intellij.util.messages.MessageBusConnection; import com.intellij.util.messages.Topic; import git4idea.GitBranch; -import git4idea.GitUtil; import git4idea.branch.GitBranchesCollection; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.io.File; import java.util.Collection; /** @@ -67,30 +58,14 @@ import java.util.Collection; * * @author Kirill Likhodedov */ -public final class GitRepository implements Disposable { +public interface GitRepository { - public static final Topic GIT_REPO_CHANGE = Topic.create("GitRepository change", GitRepositoryChangeListener.class); - - private static final Object STUB_OBJECT = new Object(); - - private final Project myProject; - private final VirtualFile myRootDir; - private final GitRepositoryReader myReader; - private final VirtualFile myGitDir; - private final MessageBus myMessageBus; - private final GitUntrackedFilesHolder myUntrackedFilesHolder; - private final QueueProcessor myNotifier; - - private volatile State myState; - private volatile String myCurrentRevision; - private volatile GitBranch myCurrentBranch; - private volatile GitBranchesCollection myBranches = GitBranchesCollection.EMPTY; - private volatile GitConfig myConfig; + Topic GIT_REPO_CHANGE = Topic.create("GitRepository change", GitRepositoryChangeListener.class); /** * Current state of the repository. */ - public enum State { + enum State { /** * HEAD is on branch, no merge process is in progress (and no rebase as well). */ @@ -121,141 +96,40 @@ public final class GitRepository implements Disposable { * GitRepository tracks the updates of some information about Git repository, caches this information and provides methods to access to * it. The pieces of this information are called Topics. They can be used to update the repository. */ - public enum TrackedTopic { - STATE { - @Override void update(GitRepository repository) { - repository.updateState(); - } - }, - CURRENT_REVISION { - @Override void update(GitRepository repository) { - repository.updateCurrentRevision(); - } - }, - CURRENT_BRANCH { - @Override void update(GitRepository repository) { - repository.updateCurrentBranch(); - } - }, - BRANCHES { - @Override void update(GitRepository repository) { - repository.updateBranchList(); - } - }, - CONFIG { - @Override void update(GitRepository repository) { - repository.updateConfig(); - } - }, - ALL_CURRENT { - @Override void update(GitRepository repository) { - STATE.update(repository); - CURRENT_REVISION.update(repository); - CURRENT_BRANCH.update(repository); - } - }, - ALL { - @Override void update(GitRepository repository) { - ALL_CURRENT.update(repository); - BRANCHES.update(repository); - CONFIG.update(repository); - } - }; - - abstract void update(GitRepository repository); - } - - /** - * Get the GitRepository instance from the {@link GitRepositoryManager}. - * If you need to have an instance of GitRepository for a repository outside the project, use - * {@link #getLightInstance(com.intellij.openapi.vfs.VirtualFile, com.intellij.openapi.project.Project, com.intellij.openapi.Disposable)}. - */ - private GitRepository(@NotNull VirtualFile rootDir, @NotNull Project project, @NotNull Disposable parentDisposable) { - myRootDir = rootDir; - myProject = project; - Disposer.register(parentDisposable, this); - - myGitDir = GitUtil.findGitDir(myRootDir); - assert myGitDir != null : ".git directory wasn't found under " + rootDir.getPresentableUrl(); - - myReader = new GitRepositoryReader(VfsUtil.virtualToIoFile(myGitDir)); - - myUntrackedFilesHolder = new GitUntrackedFilesHolder(this); - Disposer.register(this, myUntrackedFilesHolder); - - myMessageBus = project.getMessageBus(); - myNotifier = new QueueProcessor(new NotificationConsumer(myProject, myMessageBus), myProject.getDisposed()); - update(TrackedTopic.ALL); - } - - /** - * Returns the temporary light instance of GitRepository. - * It lacks functionality of auto-updating GitRepository on Git internal files change, and also stored a stub instance of - * {@link GitUntrackedFilesHolder}. - */ - @NotNull - public static GitRepository getLightInstance(@NotNull VirtualFile root, @NotNull Project project, @NotNull Disposable parentDisposable) { - return new GitRepository(root, project, parentDisposable); - } - - /** - * Returns the full-functional instance of GitRepository - with UntrackedFilesHolder and GitRepositoryUpdater. - * This is used for repositories registered in project, and should be optained via {@link GitRepositoryManager}. - */ - public static GitRepository getFullInstance(@NotNull VirtualFile root, @NotNull Project project, @NotNull Disposable parentDisposable) { - GitRepository repository = new GitRepository(root, project, parentDisposable); - repository.myUntrackedFilesHolder.setupVfsListener(project); - repository.setupUpdater(); - return repository; - } - - private void setupUpdater() { - GitRepositoryUpdater updater = new GitRepositoryUpdater(this); - Disposer.register(this, updater); - } - - @Override - public void dispose() { + enum TrackedTopic { + STATE, + CURRENT_REVISION, + CURRENT_BRANCH, + BRANCHES, + CONFIG, + ALL_CURRENT, + ALL } @NotNull - public VirtualFile getRoot() { - return myRootDir; - } + VirtualFile getRoot(); @NotNull - public VirtualFile getGitDir() { - return myGitDir; - } + VirtualFile getGitDir(); @NotNull - public String getPresentableUrl() { - return getRoot().getPresentableUrl(); - } + String getPresentableUrl(); @NotNull - public Project getProject() { - return myProject; - } + Project getProject(); @NotNull - public GitUntrackedFilesHolder getUntrackedFilesHolder() { - return myUntrackedFilesHolder; - } + GitUntrackedFilesHolder getUntrackedFilesHolder(); @NotNull - public State getState() { - return myState; - } + State getState(); /** * Returns the hash of the revision, which HEAD currently points to. * Returns null only in the case of a fresh repository, when no commit have been made. */ @Nullable - public String getCurrentRevision() { - return myCurrentRevision; - } + String getCurrentRevision(); /** * Returns the current branch of this Git repository. @@ -264,22 +138,13 @@ public final class GitRepository implements Disposable { * Returns null, if the repository is not on a branch and not in the REBASING state. */ @Nullable - public GitBranch getCurrentBranch() { - return myCurrentBranch; - } - - /** - * @return local and remote branches in this repository. - */ - @NotNull - public GitBranchesCollection getBranches() { - return new GitBranchesCollection(myBranches); - } + GitBranch getCurrentBranch(); @NotNull - public GitConfig getConfig() { - return myConfig; - } + GitBranchesCollection getBranches(); + + @NotNull + GitConfig getConfig(); /** * Returns remotes defined in this Git repository. @@ -291,125 +156,26 @@ public final class GitRepository implements Disposable { * @return GitRemotes defined for this repository. */ @NotNull - public Collection getRemotes() { - return myConfig.getRemotes(); - } + Collection getRemotes(); - public boolean isMergeInProgress() { - return getState() == State.MERGING; - } + boolean isMergeInProgress(); - public boolean isRebaseInProgress() { - return getState() == State.REBASING; - } + boolean isRebaseInProgress(); - public boolean isOnBranch() { - return getState() != State.DETACHED && getState() != State.REBASING; - } + boolean isOnBranch(); /** * @return true if current repository is "fresh", i.e. if no commits have been made yet. */ - public boolean isFresh() { - return getCurrentRevision() == null; - } + boolean isFresh(); - public void addListener(GitRepositoryChangeListener listener) { - MessageBusConnection connection = myMessageBus.connect(); - Disposer.register(this, connection); - connection.subscribe(GIT_REPO_CHANGE, listener); - } + void addListener(@NotNull GitRepositoryChangeListener listener); /** * Synchronously updates the GitRepository by reading information from the specified topics. */ - public void update(TrackedTopic... topics) { - for (TrackedTopic topic : topics) { - topic.update(this); - } - notifyListeners(); - } - - private void updateConfig() { - File configFile = new File(VfsUtil.virtualToIoFile(myGitDir), "config"); - myConfig = GitConfig.read(configFile); - } + void update(TrackedTopic... topics); - /** - * Reads current state and notifies listeners about the change. - */ - private void updateState() { - myState = myReader.readState(); - } - - /** - * Reads current revision and notifies listeners about the change. - */ - private void updateCurrentRevision() { - myCurrentRevision = myReader.readCurrentRevision(); - } - - /** - * Reads current branch and notifies listeners about the change. - */ - private void updateCurrentBranch() { - myCurrentBranch = myReader.readCurrentBranch(); - } - - private void updateBranchList() { - myBranches = myReader.readBranches(); - } - - private void notifyListeners() { - myNotifier.add(STUB_OBJECT); // we don't have parameters for listeners - } - - private static class NotificationConsumer implements Consumer { - - private final Project myProject; - private final MessageBus myMessageBus; - - NotificationConsumer(Project project, MessageBus messageBus) { - myProject = project; - myMessageBus = messageBus; - } - - @Override - public void consume(Object o) { - if (!Disposer.isDisposed(myProject)) { - myMessageBus.syncPublisher(GIT_REPO_CHANGE).repositoryChanged(); - } - } - } - - public String toLogString() { - return String.format("GitRepository{myCurrentBranch=%s, myCurrentRevision='%s', myState=%s, myRootDir=%s}", - myCurrentBranch, myCurrentRevision, myState, myRootDir); - } - - @Override - public String toString() { - return getPresentableUrl(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - GitRepository that = (GitRepository)o; - - if (myProject != null ? !myProject.equals(that.myProject) : that.myProject != null) return false; - if (myRootDir != null ? !myRootDir.equals(that.myRootDir) : that.myRootDir != null) return false; - - return true; - } - - @Override - public int hashCode() { - int result = myProject != null ? myProject.hashCode() : 0; - result = 31 * result + (myRootDir != null ? myRootDir.hashCode() : 0); - return result; - } + String toLogString(); } diff --git a/plugins/git4idea/src/git4idea/repo/GitRepositoryImpl.java b/plugins/git4idea/src/git4idea/repo/GitRepositoryImpl.java new file mode 100644 index 000000000000..8982ac8ea3fd --- /dev/null +++ b/plugins/git4idea/src/git4idea/repo/GitRepositoryImpl.java @@ -0,0 +1,314 @@ +/* + * Copyright 2000-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package git4idea.repo; + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.Consumer; +import com.intellij.util.concurrency.QueueProcessor; +import com.intellij.util.messages.MessageBus; +import com.intellij.util.messages.MessageBusConnection; +import git4idea.GitBranch; +import git4idea.GitUtil; +import git4idea.branch.GitBranchesCollection; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.util.Collection; + +/** + * @author Kirill Likhodedov + */ +public class GitRepositoryImpl implements GitRepository, Disposable { + + private static final Object STUB_OBJECT = new Object(); + + private final Project myProject; + private final VirtualFile myRootDir; + private final GitRepositoryReader myReader; + private final VirtualFile myGitDir; + private final MessageBus myMessageBus; + private final GitUntrackedFilesHolder myUntrackedFilesHolder; + private final QueueProcessor myNotifier; + + private volatile State myState; + private volatile String myCurrentRevision; + private volatile GitBranch myCurrentBranch; + private volatile GitBranchesCollection myBranches = GitBranchesCollection.EMPTY; + private volatile GitConfig myConfig; + + /** + * Get the GitRepository instance from the {@link GitRepositoryManager}. + * If you need to have an instance of GitRepository for a repository outside the project, use + * {@link #getLightInstance(com.intellij.openapi.vfs.VirtualFile, com.intellij.openapi.project.Project, com.intellij.openapi.Disposable)}. + */ + private GitRepositoryImpl(@NotNull VirtualFile rootDir, @NotNull Project project, @NotNull Disposable parentDisposable) { + myRootDir = rootDir; + myProject = project; + Disposer.register(parentDisposable, this); + + myGitDir = GitUtil.findGitDir(myRootDir); + assert myGitDir != null : ".git directory wasn't found under " + rootDir.getPresentableUrl(); + + myReader = new GitRepositoryReader(VfsUtil.virtualToIoFile(myGitDir)); + + myUntrackedFilesHolder = new GitUntrackedFilesHolder(this); + Disposer.register(this, myUntrackedFilesHolder); + + myMessageBus = project.getMessageBus(); + myNotifier = new QueueProcessor(new NotificationConsumer(myProject, myMessageBus), myProject.getDisposed()); + update(TrackedTopic.ALL); + } + + /** + * Returns the temporary light instance of GitRepository. + * It lacks functionality of auto-updating GitRepository on Git internal files change, and also stored a stub instance of + * {@link GitUntrackedFilesHolder}. + */ + @NotNull + public static GitRepository getLightInstance(@NotNull VirtualFile root, @NotNull Project project, @NotNull Disposable parentDisposable) { + return new GitRepositoryImpl(root, project, parentDisposable); + } + + /** + * Returns the full-functional instance of GitRepository - with UntrackedFilesHolder and GitRepositoryUpdater. + * This is used for repositories registered in project, and should be optained via {@link GitRepositoryManager}. + */ + public static GitRepository getFullInstance(@NotNull VirtualFile root, @NotNull Project project, @NotNull Disposable parentDisposable) { + GitRepositoryImpl repository = new GitRepositoryImpl(root, project, parentDisposable); + repository.myUntrackedFilesHolder.setupVfsListener(project); + repository.setupUpdater(); + return repository; + } + + private void setupUpdater() { + GitRepositoryUpdater updater = new GitRepositoryUpdater(this); + Disposer.register(this, updater); + } + + @Override + public void dispose() { + } + + @Override + @NotNull + public VirtualFile getRoot() { + return myRootDir; + } + + @Override + @NotNull + public VirtualFile getGitDir() { + return myGitDir; + } + + @Override + @NotNull + public String getPresentableUrl() { + return getRoot().getPresentableUrl(); + } + + @Override + @NotNull + public Project getProject() { + return myProject; + } + + @Override + @NotNull + public GitUntrackedFilesHolder getUntrackedFilesHolder() { + return myUntrackedFilesHolder; + } + + @Override + @NotNull + public State getState() { + return myState; + } + + @Override + @Nullable + public String getCurrentRevision() { + return myCurrentRevision; + } + + @Override + @Nullable + public GitBranch getCurrentBranch() { + return myCurrentBranch; + } + + /** + * @return local and remote branches in this repository. + */ + @Override + @NotNull + public GitBranchesCollection getBranches() { + return new GitBranchesCollection(myBranches); + } + + @Override + @NotNull + public GitConfig getConfig() { + return myConfig; + } + + @Override + @NotNull + public Collection getRemotes() { + return myConfig.getRemotes(); + } + + @Override + public boolean isMergeInProgress() { + return getState() == State.MERGING; + } + + @Override + public boolean isRebaseInProgress() { + return getState() == State.REBASING; + } + + @Override + public boolean isOnBranch() { + return getState() != State.DETACHED && getState() != State.REBASING; + } + + @Override + public boolean isFresh() { + return getCurrentRevision() == null; + } + + @Override + public void addListener(@NotNull GitRepositoryChangeListener listener) { + MessageBusConnection connection = myMessageBus.connect(); + Disposer.register(this, connection); + connection.subscribe(GIT_REPO_CHANGE, listener); + } + + @Override + public void update(TrackedTopic... topics) { + for (TrackedTopic topic : topics) { + switch (topic) { + case STATE: updateState(); break; + case CURRENT_REVISION: updateCurrentRevision(); break; + case CURRENT_BRANCH: updateCurrentBranch(); break; + case BRANCHES: updateBranchList(); break; + case CONFIG: updateConfig(); break; + case ALL_CURRENT: + updateState(); + updateCurrentRevision(); + updateCurrentBranch(); + break; + case ALL: + updateState(); + updateCurrentRevision(); + updateCurrentBranch(); + updateBranchList(); + updateConfig(); + break; + } + } + notifyListeners(); + } + + private void updateConfig() { + File configFile = new File(VfsUtil.virtualToIoFile(myGitDir), "config"); + myConfig = GitConfig.read(configFile); + } + + /** + * Reads current state and notifies listeners about the change. + */ + private void updateState() { + myState = myReader.readState(); + } + + /** + * Reads current revision and notifies listeners about the change. + */ + private void updateCurrentRevision() { + myCurrentRevision = myReader.readCurrentRevision(); + } + + /** + * Reads current branch and notifies listeners about the change. + */ + private void updateCurrentBranch() { + myCurrentBranch = myReader.readCurrentBranch(); + } + + private void updateBranchList() { + myBranches = myReader.readBranches(); + } + + private void notifyListeners() { + myNotifier.add(STUB_OBJECT); // we don't have parameters for listeners + } + + private static class NotificationConsumer implements Consumer { + + private final Project myProject; + private final MessageBus myMessageBus; + + NotificationConsumer(Project project, MessageBus messageBus) { + myProject = project; + myMessageBus = messageBus; + } + + @Override + public void consume(Object o) { + if (!Disposer.isDisposed(myProject)) { + myMessageBus.syncPublisher(GIT_REPO_CHANGE).repositoryChanged(); + } + } + } + + @Override + public String toLogString() { + return String.format("GitRepository{myCurrentBranch=%s, myCurrentRevision='%s', myState=%s, myRootDir=%s}", + myCurrentBranch, myCurrentRevision, myState, myRootDir); + } + + @Override + public String toString() { + return getPresentableUrl(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + GitRepositoryImpl that = (GitRepositoryImpl)o; + + if (myProject != null ? !myProject.equals(that.myProject) : that.myProject != null) return false; + if (myRootDir != null ? !myRootDir.equals(that.myRootDir) : that.myRootDir != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = myProject != null ? myProject.hashCode() : 0; + result = 31 * result + (myRootDir != null ? myRootDir.hashCode() : 0); + return result; + } +} diff --git a/plugins/git4idea/src/git4idea/repo/GitRepositoryManagerImpl.java b/plugins/git4idea/src/git4idea/repo/GitRepositoryManagerImpl.java index 8b80d326ca11..362b4c7a74d2 100644 --- a/plugins/git4idea/src/git4idea/repo/GitRepositoryManagerImpl.java +++ b/plugins/git4idea/src/git4idea/repo/GitRepositoryManagerImpl.java @@ -203,7 +203,7 @@ public class GitRepositoryManagerImpl extends AbstractProjectComponent implement } private GitRepository createGitRepository(VirtualFile root) { - GitRepository repository = GitRepository.getFullInstance(root, myProject, this); + GitRepository repository = GitRepositoryImpl.getFullInstance(root, myProject, this); for (GitRepositoryChangeListener listener : myListeners) { repository.addListener(listener); } diff --git a/plugins/git4idea/src/git4idea/roots/GitIntegrationEnabler.java b/plugins/git4idea/src/git4idea/roots/GitIntegrationEnabler.java index da5d6886e619..f602ca2175de 100644 --- a/plugins/git4idea/src/git4idea/roots/GitIntegrationEnabler.java +++ b/plugins/git4idea/src/git4idea/roots/GitIntegrationEnabler.java @@ -79,7 +79,7 @@ public class GitIntegrationEnabler { GitCommandResult result = myGit.init(myProject, projectDir); if (result.success()) { refreshGitDir(projectDir); - notificator.notifySuccess("", "Created Git repository in \n" + projectDir.getPresentableUrl()); + notificator.notifySuccess("", "Created Git repository in " + projectDir.getPresentableUrl()); return true; } else { diff --git a/plugins/git4idea/src/git4idea/stash/GitChangesSaver.java b/plugins/git4idea/src/git4idea/stash/GitChangesSaver.java index 7b1bb953cf38..fddc877d85fe 100644 --- a/plugins/git4idea/src/git4idea/stash/GitChangesSaver.java +++ b/plugins/git4idea/src/git4idea/stash/GitChangesSaver.java @@ -26,6 +26,7 @@ import com.intellij.openapi.vcs.changes.ChangeListManagerEx; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.continuation.ContinuationContext; import git4idea.GitVcs; +import git4idea.commands.Git; import git4idea.config.GitVcsSettings; import git4idea.merge.GitConflictResolver; import org.jetbrains.annotations.NotNull; @@ -46,6 +47,7 @@ public abstract class GitChangesSaver { protected final Project myProject; protected final ChangeListManagerEx myChangeManager; + @NotNull protected final Git myGit; protected final ProgressIndicator myProgressIndicator; protected final String myStashMessage; @@ -60,25 +62,27 @@ public abstract class GitChangesSaver { * Returns an instance of the proper GitChangesSaver depending on the chosen save changes policy. * @return {@link GitStashChangesSaver}, {@link GitShelveChangesSaver} or {@link GitDumbChangesSaver} */ - public static GitChangesSaver getSaver(Project project, ProgressIndicator progressIndicator, String stashMessage) { + public static GitChangesSaver getSaver(Project project, @NotNull Git git, ProgressIndicator progressIndicator, String stashMessage) { final GitVcsSettings settings = GitVcsSettings.getInstance(project); if (settings == null) { - return getDefaultSaver(project, progressIndicator, stashMessage); + return getDefaultSaver(project, git, progressIndicator, stashMessage); } switch (settings.updateChangesPolicy()) { - case STASH: return new GitStashChangesSaver(project, progressIndicator, stashMessage); - case SHELVE: return new GitShelveChangesSaver(project, progressIndicator, stashMessage); + case STASH: return new GitStashChangesSaver(project, git, progressIndicator, stashMessage); + case SHELVE: return new GitShelveChangesSaver(project, git, progressIndicator, stashMessage); } - return getDefaultSaver(project, progressIndicator, stashMessage); + return getDefaultSaver(project, git, progressIndicator, stashMessage); } // In the case of illegal value in the settings or impossibility to get the settings. - private static GitChangesSaver getDefaultSaver(Project project, ProgressIndicator progressIndicator, String stashMessage) { - return new GitStashChangesSaver(project, progressIndicator, stashMessage); + private static GitChangesSaver getDefaultSaver(Project project, @NotNull Git git, ProgressIndicator progressIndicator, + String stashMessage) { + return new GitStashChangesSaver(project, git, progressIndicator, stashMessage); } - protected GitChangesSaver(Project project, ProgressIndicator indicator, String stashMessage) { + protected GitChangesSaver(Project project, @NotNull Git git, ProgressIndicator indicator, String stashMessage) { myProject = project; + myGit = git; myProgressIndicator = indicator; myStashMessage = stashMessage; myChangeManager = (ChangeListManagerEx)ChangeListManagerEx.getInstance(myProject); diff --git a/plugins/git4idea/src/git4idea/stash/GitShelveChangesSaver.java b/plugins/git4idea/src/git4idea/stash/GitShelveChangesSaver.java index 55d2829e6d87..14cd4d97817d 100644 --- a/plugins/git4idea/src/git4idea/stash/GitShelveChangesSaver.java +++ b/plugins/git4idea/src/git4idea/stash/GitShelveChangesSaver.java @@ -26,6 +26,7 @@ import com.intellij.openapi.vcs.changes.shelf.ShelvedChangesViewManager; import com.intellij.openapi.vcs.impl.LocalChangesUnderRoots; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.continuation.ContinuationContext; +import git4idea.commands.Git; import git4idea.i18n.GitBundle; import git4idea.rollback.GitRollbackEnvironment; import org.jetbrains.annotations.NotNull; @@ -41,8 +42,8 @@ public class GitShelveChangesSaver extends GitChangesSaver { private final ShelvedChangesViewManager myShelveViewManager; private ShelvedChangeList myShelvedChangeList; - public GitShelveChangesSaver(Project project, ProgressIndicator indicator, String stashMessage) { - super(project, indicator, stashMessage); + public GitShelveChangesSaver(Project project, Git git, ProgressIndicator indicator, String stashMessage) { + super(project, git, indicator, stashMessage); myShelveManager = ShelveChangesManager.getInstance(myProject); myShelveViewManager = ShelvedChangesViewManager.getInstance(myProject); } diff --git a/plugins/git4idea/src/git4idea/stash/GitStashChangesSaver.java b/plugins/git4idea/src/git4idea/stash/GitStashChangesSaver.java index c81bf8dbc64b..3f42006cd2b7 100644 --- a/plugins/git4idea/src/git4idea/stash/GitStashChangesSaver.java +++ b/plugins/git4idea/src/git4idea/stash/GitStashChangesSaver.java @@ -18,6 +18,7 @@ package git4idea.stash; import com.intellij.notification.Notification; import com.intellij.notification.NotificationListener; import com.intellij.notification.NotificationType; +import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; @@ -31,6 +32,7 @@ import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.continuation.ContinuationContext; import git4idea.GitVcs; +import git4idea.PlatformFacade; import git4idea.commands.*; import git4idea.config.GitVcsSettings; import git4idea.convert.GitFileSeparatorConverter; @@ -54,8 +56,8 @@ public class GitStashChangesSaver extends GitChangesSaver { private static final Logger LOG = Logger.getInstance(GitStashChangesSaver.class); private final Set myStashedRoots = new HashSet(); // save stashed roots to unstash only them - public GitStashChangesSaver(Project project, ProgressIndicator progressIndicator, String stashMessage) { - super(project, progressIndicator, stashMessage); + public GitStashChangesSaver(Project project, @NotNull Git git, ProgressIndicator progressIndicator, String stashMessage) { + super(project, git, progressIndicator, stashMessage); } @Override @@ -85,7 +87,7 @@ public class GitStashChangesSaver extends GitChangesSaver { } } - boolean conflictsResolved = new UnstashConflictResolver(myProject, myStashedRoots, myParams).merge(); + boolean conflictsResolved = new UnstashConflictResolver(myProject, myGit, myStashedRoots, myParams).merge(); if (conflictsResolved) { LOG.info("load: all conflicts resolved, dropping stash in " + myStashedRoots); for (VirtualFile root : conflictedRoots) { @@ -194,8 +196,8 @@ public class GitStashChangesSaver extends GitChangesSaver { private final Set myStashedRoots; - public UnstashConflictResolver(@NotNull Project project, @NotNull Set stashedRoots, @Nullable Params params) { - super(project, stashedRoots, makeParamsOrUse(params)); + public UnstashConflictResolver(@NotNull Project project, @NotNull Git git, @NotNull Set stashedRoots, @Nullable Params params) { + super(project, git, ServiceManager.getService(PlatformFacade.class), stashedRoots, makeParamsOrUse(params)); myStashedRoots = stashedRoots; } diff --git a/plugins/git4idea/src/git4idea/ui/GitUnstashDialog.java b/plugins/git4idea/src/git4idea/ui/GitUnstashDialog.java index f2eefbd1f9f2..0eeb561ce5a8 100644 --- a/plugins/git4idea/src/git4idea/ui/GitUnstashDialog.java +++ b/plugins/git4idea/src/git4idea/ui/GitUnstashDialog.java @@ -18,6 +18,7 @@ package git4idea.ui; import com.intellij.notification.Notification; import com.intellij.notification.NotificationListener; import com.intellij.notification.NotificationType; +import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; @@ -35,6 +36,7 @@ import com.intellij.util.Consumer; import git4idea.GitBranch; import git4idea.GitRevisionNumber; import git4idea.GitVcs; +import git4idea.PlatformFacade; import git4idea.actions.GitShowAllSubmittedFilesAction; import git4idea.commands.*; import git4idea.config.GitVersionSpecialty; @@ -449,7 +451,8 @@ public class GitUnstashDialog extends DialogWrapper { private final StashInfo myStashInfo; public UnstashConflictResolver(Project project, VirtualFile root, StashInfo stashInfo) { - super(project, Collections.singleton(root), makeParams(stashInfo)); + super(project, ServiceManager.getService(Git.class), ServiceManager.getService(PlatformFacade.class), + Collections.singleton(root), makeParams(stashInfo)); myRoot = root; myStashInfo = stashInfo; } diff --git a/plugins/git4idea/src/git4idea/update/GitMergeUpdater.java b/plugins/git4idea/src/git4idea/update/GitMergeUpdater.java index a95da045a147..1c6a940ec92b 100644 --- a/plugins/git4idea/src/git4idea/update/GitMergeUpdater.java +++ b/plugins/git4idea/src/git4idea/update/GitMergeUpdater.java @@ -15,6 +15,7 @@ */ package git4idea.update; +import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; @@ -29,11 +30,11 @@ import com.intellij.openapi.vcs.update.UpdatedFiles; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.ui.UIUtil; import git4idea.GitUtil; +import git4idea.PlatformFacade; import git4idea.branch.GitBranchPair; import git4idea.commands.*; import git4idea.merge.GitConflictResolver; import git4idea.merge.GitMerger; -import git4idea.commands.GitMessageWithFilesDetector; import git4idea.util.GitUIUtil; import git4idea.util.UntrackedFilesNotifier; import org.jetbrains.annotations.NotNull; @@ -51,12 +52,12 @@ public class GitMergeUpdater extends GitUpdater { private final ChangeListManager myChangeListManager; - public GitMergeUpdater(Project project, + public GitMergeUpdater(Project project, @NotNull Git git, VirtualFile root, final Map trackedBranches, ProgressIndicator progressIndicator, UpdatedFiles updatedFiles) { - super(project, root, trackedBranches, progressIndicator, updatedFiles); + super(project, git, root, trackedBranches, progressIndicator, updatedFiles); myChangeListManager = ChangeListManager.getInstance(myProject); } @@ -111,7 +112,7 @@ public class GitMergeUpdater extends GitUpdater { if (error == MergeError.CONFLICT) { LOG.info("Conflict detected"); final boolean allMerged = - new MyConflictResolver(myProject, merger, myRoot).merge(); + new MyConflictResolver(myProject, myGit, merger, myRoot).merge(); return allMerged ? GitUpdateResult.SUCCESS : GitUpdateResult.INCOMPLETE; } else if (error == MergeError.LOCAL_CHANGES) { @@ -134,7 +135,8 @@ public class GitMergeUpdater extends GitUpdater { } else if (untrackedFilesWouldBeOverwrittenByMergeDetector.wasMessageDetected()) { LOG.info("handleMergeFailure: untracked files would be overwritten by merge"); - UntrackedFilesNotifier.notifyUntrackedFilesOverwrittenBy(myProject, untrackedFilesWouldBeOverwrittenByMergeDetector.getFiles(), "merge"); + UntrackedFilesNotifier.notifyUntrackedFilesOverwrittenBy(myProject, ServiceManager.getService(myProject, PlatformFacade.class), + untrackedFilesWouldBeOverwrittenByMergeDetector.getFiles(), "merge", null); return GitUpdateResult.ERROR; } else { @@ -278,8 +280,8 @@ public class GitMergeUpdater extends GitUpdater { private final GitMerger myMerger; private final VirtualFile myRoot; - public MyConflictResolver(Project project, GitMerger merger, VirtualFile root) { - super(project, Collections.singleton(root), makeParams()); + public MyConflictResolver(Project project, @NotNull Git git, GitMerger merger, VirtualFile root) { + super(project, git, ServiceManager.getService(git4idea.PlatformFacade.class), Collections.singleton(root), makeParams()); myMerger = merger; myRoot = root; } diff --git a/plugins/git4idea/src/git4idea/update/GitRebaseUpdater.java b/plugins/git4idea/src/git4idea/update/GitRebaseUpdater.java index b8602acdc4b0..32f0418fa0f2 100644 --- a/plugins/git4idea/src/git4idea/update/GitRebaseUpdater.java +++ b/plugins/git4idea/src/git4idea/update/GitRebaseUpdater.java @@ -15,6 +15,7 @@ */ package git4idea.update; +import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; @@ -23,14 +24,15 @@ import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.update.UpdatedFiles; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.ui.UIUtil; +import git4idea.PlatformFacade; import git4idea.branch.GitBranchPair; import git4idea.commands.*; import git4idea.merge.GitConflictResolver; -import git4idea.commands.GitMessageWithFilesDetector; import git4idea.rebase.GitRebaseProblemDetector; import git4idea.rebase.GitRebaser; import git4idea.util.GitUIUtil; import git4idea.util.UntrackedFilesNotifier; +import org.jetbrains.annotations.NotNull; import java.util.Collections; import java.util.Map; @@ -44,13 +46,12 @@ public class GitRebaseUpdater extends GitUpdater { private static final Logger LOG = Logger.getInstance(GitRebaseUpdater.class.getName()); private final GitRebaser myRebaser; - public GitRebaseUpdater(Project project, - VirtualFile root, - final Map trackedBranches, + public GitRebaseUpdater(@NotNull Project project, @NotNull Git git, @NotNull VirtualFile root, + @NotNull final Map trackedBranches, ProgressIndicator progressIndicator, UpdatedFiles updatedFiles) { - super(project, root, trackedBranches, progressIndicator, updatedFiles); - myRebaser = new GitRebaser(myProject, myProgressIndicator); + super(project, git, root, trackedBranches, progressIndicator, updatedFiles); + myRebaser = new GitRebaser(myProject, git, myProgressIndicator); } @Override public boolean isSaveNeeded() { @@ -103,11 +104,12 @@ public class GitRebaseUpdater extends GitUpdater { final GitMessageWithFilesDetector untrackedWouldBeOverwrittenDetector) { if (rebaseConflictDetector.isMergeConflict()) { LOG.info("handleRebaseFailure merge conflict"); - final boolean allMerged = new MyConflictResolver(myProject, myRoot, myRebaser).merge(); + final boolean allMerged = new MyConflictResolver(myProject, myGit, myRoot, myRebaser).merge(); return allMerged ? GitUpdateResult.SUCCESS : GitUpdateResult.INCOMPLETE; } else if (untrackedWouldBeOverwrittenDetector.wasMessageDetected()) { LOG.info("handleRebaseFailure: untracked files would be overwritten by checkout"); - UntrackedFilesNotifier.notifyUntrackedFilesOverwrittenBy(myProject, untrackedWouldBeOverwrittenDetector.getFiles(), "rebase"); + UntrackedFilesNotifier.notifyUntrackedFilesOverwrittenBy(myProject, ServiceManager.getService(myProject, PlatformFacade.class), + untrackedWouldBeOverwrittenDetector.getFiles(), "rebase", null); return GitUpdateResult.ERROR; } else { LOG.info("handleRebaseFailure error " + pullHandler.errors()); @@ -156,8 +158,8 @@ public class GitRebaseUpdater extends GitUpdater { private final GitRebaser myRebaser; private final VirtualFile myRoot; - public MyConflictResolver(Project project, VirtualFile root, GitRebaser rebaser) { - super(project, Collections.singleton(root), makeParams()); + public MyConflictResolver(Project project, @NotNull Git git, VirtualFile root, GitRebaser rebaser) { + super(project, git, ServiceManager.getService(PlatformFacade.class), Collections.singleton(root), makeParams()); myRebaser = rebaser; myRoot = root; } diff --git a/plugins/git4idea/src/git4idea/update/GitUpdateProcess.java b/plugins/git4idea/src/git4idea/update/GitUpdateProcess.java index a270dbfe7e3c..e1390c294642 100644 --- a/plugins/git4idea/src/git4idea/update/GitUpdateProcess.java +++ b/plugins/git4idea/src/git4idea/update/GitUpdateProcess.java @@ -16,6 +16,7 @@ package git4idea.update; import com.intellij.notification.NotificationType; +import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; @@ -30,6 +31,7 @@ import com.intellij.util.text.DateFormatUtil; import git4idea.GitBranch; import git4idea.GitVcs; import git4idea.branch.GitBranchPair; +import git4idea.commands.Git; import git4idea.merge.GitConflictResolver; import git4idea.merge.GitMergeCommittingConflictResolver; import git4idea.merge.GitMerger; @@ -52,6 +54,7 @@ public class GitUpdateProcess { private final Project myProject; private final GitVcs myVcs; + @NotNull private final Git myGit; private final Set myRoots; private final UpdatedFiles myUpdatedFiles; private final ProgressIndicator myProgressIndicator; @@ -62,7 +65,7 @@ public class GitUpdateProcess { private boolean myResult; private final Map myUpdaters; private final Collection myRootsToSave; - + public enum UpdateMethod { MERGE, REBASE, @@ -75,10 +78,11 @@ public class GitUpdateProcess { myProject = project; myRoots = roots; myVcs = GitVcs.getInstance(project); + myGit = ServiceManager.getService(Git.class); myUpdatedFiles = updatedFiles; myProgressIndicator = progressIndicator; myMerger = new GitMerger(myProject); - mySaver = GitChangesSaver.getSaver(myProject, myProgressIndicator, + mySaver = GitChangesSaver.getSaver(myProject, myGit, myProgressIndicator, "Uncommitted changes before update operation at " + DateFormatUtil.formatDateTime(Clock.getTime())); myUpdaters = new HashMap(); myRootsToSave = new HashSet(1); @@ -134,11 +138,11 @@ public class GitUpdateProcess { for (VirtualFile root : myRoots) { final GitUpdater updater; if (updateMethod == UpdateMethod.MERGE) { - updater = new GitMergeUpdater(myProject, root, myTrackedBranches, myProgressIndicator, myUpdatedFiles); + updater = new GitMergeUpdater(myProject, myGit, root, myTrackedBranches, myProgressIndicator, myUpdatedFiles); } else if (updateMethod == UpdateMethod.REBASE) { - updater = new GitRebaseUpdater(myProject, root, myTrackedBranches, myProgressIndicator, myUpdatedFiles); + updater = new GitRebaseUpdater(myProject, myGit, root, myTrackedBranches, myProgressIndicator, myUpdatedFiles); } else { - updater = GitUpdater.getUpdater(myProject, myTrackedBranches, root, myProgressIndicator, myUpdatedFiles); + updater = GitUpdater.getUpdater(myProject, myGit, myTrackedBranches, root, myProgressIndicator, myUpdatedFiles); } if (updater.isUpdateNeeded()) { @@ -301,7 +305,7 @@ public class GitUpdateProcess { GitConflictResolver.Params params = new GitConflictResolver.Params(); params.setErrorNotificationTitle("Can't update"); params.setMergeDescription("You have unfinished merge. These conflicts must be resolved before update."); - return !new GitMergeCommittingConflictResolver(myProject, myMerger, mergingRoots, params, false).merge(); + return !new GitMergeCommittingConflictResolver(myProject, myGit, myMerger, mergingRoots, params, false).merge(); } /** @@ -312,7 +316,7 @@ public class GitUpdateProcess { GitConflictResolver.Params params = new GitConflictResolver.Params(); params.setErrorNotificationTitle("Can't update"); params.setMergeDescription("Unmerged files detected. These conflicts must be resolved before update."); - return !new GitMergeCommittingConflictResolver(myProject, myMerger, myRoots, params, false).merge(); + return !new GitMergeCommittingConflictResolver(myProject, myGit, myMerger, myRoots, params, false).merge(); } /** @@ -320,7 +324,7 @@ public class GitUpdateProcess { * @return true if rebase is in progress, which means that update can't continue. */ private boolean checkRebaseInProgress() { - final GitRebaser rebaser = new GitRebaser(myProject, myProgressIndicator); + final GitRebaser rebaser = new GitRebaser(myProject, myGit, myProgressIndicator); final Collection rebasingRoots = rebaser.getRebasingRoots(); if (rebasingRoots.isEmpty()) { return false; @@ -332,7 +336,7 @@ public class GitUpdateProcess { params.setMergeDescription("You have unfinished rebase process. These conflicts must be resolved before update."); params.setErrorNotificationAdditionalDescription("Then you may continue rebase.
You also may abort rebase to restore the original branch and stop rebasing."); params.setReverse(true); - return !new GitConflictResolver(myProject, rebasingRoots, params) { + return !new GitConflictResolver(myProject, myGit, ServiceManager.getService(git4idea.PlatformFacade.class), rebasingRoots, params) { @Override protected boolean proceedIfNothingToMerge() { return rebaser.continueRebase(rebasingRoots); } diff --git a/plugins/git4idea/src/git4idea/update/GitUpdater.java b/plugins/git4idea/src/git4idea/update/GitUpdater.java index d5ef3ce7923e..1735a3c58376 100644 --- a/plugins/git4idea/src/git4idea/update/GitUpdater.java +++ b/plugins/git4idea/src/git4idea/update/GitUpdater.java @@ -26,6 +26,7 @@ import git4idea.GitBranch; import git4idea.GitRevisionNumber; import git4idea.GitVcs; import git4idea.branch.GitBranchPair; +import git4idea.commands.Git; import git4idea.commands.GitCommand; import git4idea.commands.GitSimpleHandler; import git4idea.config.GitConfigUtil; @@ -44,7 +45,8 @@ import java.util.Map; public abstract class GitUpdater { private static final Logger LOG = Logger.getInstance(GitUpdater.class); - protected final @NotNull Project myProject; + @NotNull protected final Project myProject; + @NotNull protected final Git myGit; protected final @NotNull VirtualFile myRoot; protected final @NotNull Map myTrackedBranches; protected final @NotNull ProgressIndicator myProgressIndicator; @@ -54,9 +56,11 @@ public abstract class GitUpdater { protected GitRevisionNumber myBefore; // The revision that was before update - protected GitUpdater(@NotNull Project project, @NotNull VirtualFile root, @NotNull Map trackedBranches, - @NotNull ProgressIndicator progressIndicator, @NotNull UpdatedFiles updatedFiles) { + protected GitUpdater(@NotNull Project project, @NotNull Git git, @NotNull VirtualFile root, + @NotNull Map trackedBranches, @NotNull ProgressIndicator progressIndicator, + @NotNull UpdatedFiles updatedFiles) { myProject = project; + myGit = git; myRoot = root; myTrackedBranches = trackedBranches; myProgressIndicator = progressIndicator; @@ -70,39 +74,39 @@ public abstract class GitUpdater { * @return {@link GitMergeUpdater} or {@link GitRebaseUpdater}. */ @NotNull - public static GitUpdater getUpdater(@NotNull Project project, @NotNull Map trackedBranches, + public static GitUpdater getUpdater(@NotNull Project project, @NotNull Git git, @NotNull Map trackedBranches, @NotNull VirtualFile root, @NotNull ProgressIndicator progressIndicator, @NotNull UpdatedFiles updatedFiles) { final GitVcsSettings settings = GitVcsSettings.getInstance(project); if (settings == null) { - return getDefaultUpdaterForBranch(project, root, trackedBranches, progressIndicator, updatedFiles); + return getDefaultUpdaterForBranch(project, git, root, trackedBranches, progressIndicator, updatedFiles); } switch (settings.getUpdateType()) { case REBASE: - return new GitRebaseUpdater(project, root, trackedBranches, progressIndicator, updatedFiles); + return new GitRebaseUpdater(project, git, root, trackedBranches, progressIndicator, updatedFiles); case MERGE: - return new GitMergeUpdater(project, root, trackedBranches, progressIndicator, updatedFiles); + return new GitMergeUpdater(project, git, root, trackedBranches, progressIndicator, updatedFiles); case BRANCH_DEFAULT: // use default for the branch - return getDefaultUpdaterForBranch(project, root, trackedBranches, progressIndicator, updatedFiles); + return getDefaultUpdaterForBranch(project, git, root, trackedBranches, progressIndicator, updatedFiles); } - return getDefaultUpdaterForBranch(project, root, trackedBranches, progressIndicator, updatedFiles); + return getDefaultUpdaterForBranch(project, git, root, trackedBranches, progressIndicator, updatedFiles); } @NotNull - private static GitUpdater getDefaultUpdaterForBranch(@NotNull Project project, @NotNull VirtualFile root, + private static GitUpdater getDefaultUpdaterForBranch(@NotNull Project project, @NotNull Git git, @NotNull VirtualFile root, @NotNull Map trackedBranches, @NotNull ProgressIndicator progressIndicator, @NotNull UpdatedFiles updatedFiles) { try { final GitBranch branchName = GitBranch.current(project, root); final String rebase = GitConfigUtil.getValue(project, root, "branch." + branchName + ".rebase"); if (rebase != null && rebase.equalsIgnoreCase("true")) { - return new GitRebaseUpdater(project, root, trackedBranches, progressIndicator, updatedFiles); + return new GitRebaseUpdater(project, git, root, trackedBranches, progressIndicator, updatedFiles); } } catch (VcsException e) { LOG.info("getDefaultUpdaterForBranch branch", e); } - return new GitMergeUpdater(project, root, trackedBranches, progressIndicator, updatedFiles); + return new GitMergeUpdater(project, git, root, trackedBranches, progressIndicator, updatedFiles); } @NotNull diff --git a/plugins/git4idea/src/git4idea/util/GitPreservingProcess.java b/plugins/git4idea/src/git4idea/util/GitPreservingProcess.java index 8482f4ad01cd..73981378561b 100644 --- a/plugins/git4idea/src/git4idea/util/GitPreservingProcess.java +++ b/plugins/git4idea/src/git4idea/util/GitPreservingProcess.java @@ -28,6 +28,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.text.DateFormatUtil; import git4idea.GitUtil; import git4idea.Notificator; +import git4idea.commands.Git; import git4idea.merge.GitConflictResolver; import git4idea.repo.GitRepository; import git4idea.stash.GitStashChangesSaver; @@ -49,6 +50,7 @@ public class GitPreservingProcess { private static final Logger LOG = Logger.getInstance(GitPreservingProcess.class); @NotNull private final Project myProject; + @NotNull private final Git myGit; @NotNull private final Collection myRepositories; @NotNull private final String myOperationTitle; @NotNull private final String myDestinationName; @@ -61,10 +63,11 @@ public class GitPreservingProcess { private boolean myLoaded; private final Object LOAD_LOCK = new Object(); - public GitPreservingProcess(@NotNull Project project, @NotNull Collection repositories, + public GitPreservingProcess(@NotNull Project project, @NotNull Git git, @NotNull Collection repositories, @NotNull String operationTitle, @NotNull String destinationName, @NotNull ProgressIndicator indicator, @NotNull Runnable operation) { myProject = project; + myGit = git; myRepositories = repositories; myOperationTitle = operationTitle; myDestinationName = destinationName; @@ -110,7 +113,7 @@ public class GitPreservingProcess { * Configures the saver, actually notifications and texts in the GitConflictResolver used inside. */ private GitStashChangesSaver configureSaver() { - GitStashChangesSaver saver = new GitStashChangesSaver(myProject, myProgressIndicator, myStashMessage); + GitStashChangesSaver saver = new GitStashChangesSaver(myProject, myGit, myProgressIndicator, myStashMessage); MergeDialogCustomizer mergeDialogCustomizer = new MergeDialogCustomizer() { @Override public String getMultipleFileMergeDescription(Collection files) { diff --git a/plugins/git4idea/src/git4idea/util/UntrackedFilesNotifier.java b/plugins/git4idea/src/git4idea/util/UntrackedFilesNotifier.java index 800d02c2b69d..1d27df76e5a7 100644 --- a/plugins/git4idea/src/git4idea/util/UntrackedFilesNotifier.java +++ b/plugins/git4idea/src/git4idea/util/UntrackedFilesNotifier.java @@ -17,14 +17,13 @@ package git4idea.util; import com.intellij.notification.Notification; import com.intellij.notification.NotificationListener; -import com.intellij.notification.NotificationType; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.changes.ui.SelectFilesDialog; import com.intellij.openapi.vfs.VirtualFile; -import git4idea.GitVcs; -import git4idea.Notificator; +import git4idea.PlatformFacade; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.event.HyperlinkEvent; @@ -42,23 +41,31 @@ public class UntrackedFilesNotifier { /** * Displays notification about {@code untracked files would be overwritten by checkout} error. * Clicking on the link in the notification opens a simple dialog with the list of these files. - * @param operation the name of the Git operation that caused the error: {@code rebase, merge, checkout}. + * @param operation the name of the Git operation that caused the error: {@code rebase, merge, checkout}. + * @param description the content of the notification or null if the deafult content is to be used. */ - public static void notifyUntrackedFilesOverwrittenBy(final @NotNull Project project, final @NotNull Collection untrackedFiles, @NotNull final String operation) { + public static void notifyUntrackedFilesOverwrittenBy(@NotNull final Project project, @NotNull PlatformFacade platformFacade, + @NotNull final Collection untrackedFiles, + @NotNull final String operation, @Nullable String description) { final String notificationTitle = StringUtil.capitalize(operation) + " error"; - final String notificationDesc = createUntrackedFilesOverwrittenDescription(operation, false); + final String notificationDesc = description == null ? createUntrackedFilesOverwrittenDescription(operation, false) : description; final String dialogDesc = createUntrackedFilesOverwrittenDescription(operation, true); - Notificator.getInstance(project).notify(GitVcs.IMPORTANT_ERROR_NOTIFICATION, notificationTitle, notificationDesc, NotificationType.ERROR, new NotificationListener() { - @Override public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) { - SelectFilesDialog dlg = new SelectFilesDialog(project, new ArrayList(untrackedFiles), - StringUtil.stripHtml(dialogDesc, true), null, false, false) { - @Override protected Action[] createActions() { - return new Action[]{getOKAction()}; - } - }; - dlg.setTitle("Untracked Files Preventing " + StringUtil.capitalize(operation)); - dlg.show(); + platformFacade.getNotificator(project).notifyError(notificationTitle, notificationDesc, + new NotificationListener() { + @Override + public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) { + if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { + SelectFilesDialog dlg = new SelectFilesDialog(project, new ArrayList(untrackedFiles), + StringUtil.stripHtml(dialogDesc, true), null, false, false) { + @Override + protected Action[] createActions() { + return new Action[]{getOKAction()}; + } + }; + dlg.setTitle("Untracked Files Preventing " + StringUtil.capitalize(operation)); + dlg.show(); + } } }); } @@ -71,7 +78,7 @@ public class UntrackedFilesNotifier { notificationDesc = "These" + description1 + "
" + description2; } else { - notificationDesc = "Some" + description1 + "
" + description2 + " View them"; + notificationDesc = "Some" + description1 + "
" + description2 + " View them"; } return notificationDesc; } diff --git a/plugins/git4idea/tests/git4idea/cherrypick/GitAutoCommittingCherryPickTest.groovy b/plugins/git4idea/tests/git4idea/cherrypick/GitAutoCommittingCherryPickTest.groovy new file mode 100644 index 000000000000..aed9a9b0f8e7 --- /dev/null +++ b/plugins/git4idea/tests/git4idea/cherrypick/GitAutoCommittingCherryPickTest.groovy @@ -0,0 +1,251 @@ +/* + * Copyright 2000-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package git4idea.cherrypick + +import com.intellij.notification.NotificationType +import git4idea.history.browser.CherryPicker +import git4idea.history.browser.GitCommit +import org.junit.Before +import org.junit.Test + +import static git4idea.test.MockGit.* +import static git4idea.test.MockGit.OperationName.CHERRY_PICK +import static git4idea.test.MockGit.OperationName.GET_UNMERGED_FILES +import static junit.framework.Assert.assertTrue + +/** + * Cherry-pick of one or multiple commits with "commit at once" option enabled. + * + * @author Kirill Likhodedov + */ +class GitAutoCommittingCherryPickTest extends GitCherryPickTest { + + public static final String EMPTY_CHERRY_PICK = """ +# On branch master +# Your branch is ahead of 'origin/master' by 11 commits. +# +# Untracked files: +# (use "git add ..." to include in what will be committed) +# +#\t.idea/ +#\tlocal_staged.patch +#\tout/ +nothing added to commit but untracked files present (use "git add" to track) +The previous cherry-pick is now empty, possibly due to conflict resolution. +If you wish to commit it anyway, use: + + git commit --allow-empty + +Otherwise, please use 'git reset' +""" + + @Before + void setUp() { + super.setUp() + myCherryPicker = new CherryPicker(myProject, myGit, myPlatformFacade, true) + } + + @Test + void "clean tree, no conflicts, then commit & notify, no new changelists"() { + GitCommit commit = commit() + + myGit.registerOperationExecutors(new SuccessfulCherryPickExecutor(myRepository, commit)) + invokeCherryPick(commit) + + assertHeadCommit(commit) + assertOnlyDefaultChangelist() + assertNotificationShown("Cherry-pick successful", commitDetails(commit), NotificationType.INFORMATION) + } + + @Test + void "dirty tree, conflicting with commit, then show error"() { + myGit.registerOperationExecutors(new SimpleErrorOperationExecutor(CHERRY_PICK, LOCAL_CHANGES_OVERWRITTEN_BY_CHERRY_PICK)) + + def commit = commit() + invokeCherryPick(commit) + + assertNotCherryPicked() + assertOnlyDefaultChangelist() + assertNotificationShown("Cherry-pick failed", + """ + ${commitDetails(commit)}
+ Your local changes would be overwritten by cherry-pick.
+ Commit your changes or stash them to proceed. + """, + NotificationType.ERROR) + } + + @Test + void "untracked files, conflicting with commit, then show error"() { + myGit.registerOperationExecutors(new SimpleErrorOperationExecutor(CHERRY_PICK, UNTRACKED_FILES_OVERWRITTEN_BY_CHERRY_PICK)) + + def commit = commit() + invokeCherryPick(commit) + + assertNotCherryPicked() + assertOnlyDefaultChangelist() + assertNotificationShown("Cherry-pick error", + """ + ${commitDetails(commit)}
+ Some untracked working tree files would be overwritten by cherry-pick.
+ Please move, remove or add them before you can cherry-pick. View them + """, + NotificationType.ERROR) + } + + @Test + void "conflict, merge dialog, not all merged, then new & active changelist, notification"() { + prepareConflict() + myGit.registerOperationExecutors(new SimpleSuccessOperationExecutor(GET_UNMERGED_FILES, UNMERGED_FILE)) + GitCommit commit = commit() + invokeCherryPick(commit) + assertMergeDialogShown() + assertChangeLists([DEFAULT, newCommitMessage(commit)], newCommitMessage(commit)) + assertNotificationShown "Cherry-picked with conflicts", + """ + ${commitDetails(commit)}
+ Unresolved conflicts remain in the working tree. Resolve them. + """, + NotificationType.WARNING + } + + @Test + void "conflict, merge completed, then commit dialog"() { + prepareConflict() + GitCommit commit = commit() + + OKCommitDialogHandler handler = new OKCommitDialogHandler(myRepository) + myVcsHelper.registerHandler(handler) + + invokeCherryPick(commit) + assertMergeDialogShown() + assertTrue "Commit dialog was not shown", handler.wasCommitDialogShown() + } + + @Test + void "conflict, merge finished, commit succeeded, no new changelists"() { + prepareConflict() + + OKCommitDialogHandler handler = new OKCommitDialogHandler(myRepository) + myVcsHelper.registerHandler(handler) + + GitCommit commit = commit() + invokeCherryPick(commit) + assertMergeDialogShown() + assertHeadCommit(commit) + assertTrue "Commit dialog was not shown", handler.wasCommitDialogShown() + assertOnlyDefaultChangelist() + } + + @Test + void "conflict, merge ok, commit cancelled, then new & active changelist"() { + prepareConflict() + + CancelCommitDialogHandler handler = new CancelCommitDialogHandler() + myVcsHelper.registerHandler(handler) + + GitCommit commit = commit() + invokeCherryPick(commit) + assertMergeDialogShown() + assertTrue "Commit dialog was not shown", handler.wasCommitDialogShown() + assertChangeLists([DEFAULT, newCommitMessage(commit)], newCommitMessage(commit)) + } + + @Test + void "2 commits, no problems, then commit all & notify"() { + GitCommit commit1 = commit("First commit to cherry-pick") + GitCommit commit2 = commit("Second commit to cherry-pick") + myGit.registerOperationExecutors(new SuccessfulCherryPickExecutor(myRepository, commit1), + new SuccessfulCherryPickExecutor(myRepository, commit2)) + + invokeCherryPick([commit1, commit2]) + assertLastCommits commit2, commit1 + assertNotificationShown("Cherry-pick successful", notificationContent(commit1, commit2), NotificationType.INFORMATION) + } + + @Test + void "3 commits, 2nd cherry-pick conflicts with local, then 1st success, 2nd stop & error"() { + GitCommit commit1 = commit("First") + GitCommit commit2 = commit("Second") + GitCommit commit3 = commit("Third") + + myGit.registerOperationExecutors(new SuccessfulCherryPickExecutor(myRepository, commit1), + new SimpleErrorOperationExecutor(CHERRY_PICK, LOCAL_CHANGES_OVERWRITTEN_BY_CHERRY_PICK), + new SuccessfulCherryPickExecutor(myRepository, commit3)) + + invokeCherryPick([commit1, commit2, commit3]) + + assertHeadCommit(commit1) + assertNotificationShown("Cherry-pick failed", + """ + ${commitDetails(commit2)}
+ Your local changes would be overwritten by cherry-pick.
+ Commit your changes or stash them to proceed. +
+ However cherry-pick succeeded for the following commit:
+ ${commitDetails(commit1)} + """, NotificationType.ERROR) + } + + @Test + void "3 commits, 2nd conflicts with committed, then 1st success, on 2nd show merge dialog"() { + GitCommit commit1 = commit("First") + GitCommit commit2 = commit("Second") + GitCommit commit3 = commit("Third") + + myGit.registerOperationExecutors(new SuccessfulCherryPickExecutor(myRepository, commit1)) + prepareConflict() + myGit.registerOperationExecutors(new SuccessfulCherryPickExecutor(myRepository, commit3)) + + OKCommitDialogHandler handler = new OKCommitDialogHandler(myRepository) + myVcsHelper.registerHandler(handler) + invokeCherryPick([commit1, commit2, commit3]) + + assertMergeDialogShown() + assertTrue "Commit dialog was not shown", handler.wasCommitDialogShown() + assertLastCommits commit3, commit2, commit1 + } + +// @Test + void "Notify if changes have already been applied"() { + // Inspired by IDEA-73548 + myGit.registerOperationExecutors(new SimpleErrorOperationExecutor(CHERRY_PICK, EMPTY_CHERRY_PICK)) + + GitCommit commit = commit() + invokeCherryPick(commit) + + assertNotCherryPicked() + assertNotificationShown("Nothing to cherry-pick", "All changes from ${commitDetails(commit)} have already been applied", + NotificationType.WARNING) + } + +// @Test + void "1st successful, 2nd empty (all applied), then compound notification"() { + // Inspired by IDEA-73548 + GitCommit commit1 = commit() + GitCommit commit2 = commit() + myGit.registerOperationExecutors(new SuccessfulCherryPickExecutor(myRepository, commit1), + new SimpleErrorOperationExecutor(CHERRY_PICK, EMPTY_CHERRY_PICK)) + + invokeCherryPick([ commit1, commit2 ]) + + assertHeadCommit(commit1) + assertNotificationShown("Cherry-picked with problems", +"""Successfully cherry-picked ${commitDetails(commit1)}
+Not cherry-picked ${commitDetails(commit2)} - all changes have already been applied""", NotificationType.WARNING) + } + +} diff --git a/plugins/git4idea/tests/git4idea/cherrypick/GitCherryPickTest.groovy b/plugins/git4idea/tests/git4idea/cherrypick/GitCherryPickTest.groovy new file mode 100644 index 000000000000..832b5adabd83 --- /dev/null +++ b/plugins/git4idea/tests/git4idea/cherrypick/GitCherryPickTest.groovy @@ -0,0 +1,199 @@ +/* + * Copyright 2000-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package git4idea.cherrypick + +import com.intellij.mock.MockVirtualFile +import com.intellij.openapi.vcs.FilePathImpl +import com.intellij.openapi.vcs.changes.Change +import com.intellij.openapi.vcs.changes.ChangeListManager +import com.intellij.openapi.vcs.changes.LocalChangeList +import com.intellij.openapi.vcs.history.VcsRevisionNumber +import com.intellij.testFramework.vcs.MockChangeListManager +import com.intellij.testFramework.vcs.MockContentRevision +import git4idea.history.browser.CherryPicker +import git4idea.history.browser.GitCommit +import git4idea.history.browser.SHAHash +import git4idea.history.wholeTree.AbstractHash +import git4idea.repo.GitRepository +import git4idea.test.GitFastTest +import git4idea.test.GitLightRepository +import git4idea.test.MockGit +import sun.security.provider.SHA + +import static git4idea.test.MockGit.OperationName.CHERRY_PICK +import static junit.framework.Assert.assertEquals +import static junit.framework.Assert.assertTrue +import static git4idea.test.MockGit.commitMessageForCherryPick +import git4idea.test.MockVcsHelper + +/** + * Common parent for all tests on cherry-pick + * + * @author Kirill Likhodedov + */ +class GitCherryPickTest extends GitFastTest { + + public static final String DEFAULT = MockChangeListManager.DEFAULT_CHANGE_LIST_NAME; + public static final String UNMERGED_FILE = """ +100644 d87b28d6fd6e97620603e64ce70fc2f24535ec28 1\ttest.txt +100644 7b50450f5deb7cce3b5ce92ba866f1af6e58c3c6 2\ttest.txt +100644 a784477cdd0437a84751c52f72b971503deb48cb 3\ttest.txt +""" + public static final String CHERRY_PICK_CONFLICT = """ +error: could not apply ec15d8e... message +hint: after resolving the conflicts, mark the corrected paths +hint: with 'git add ' or 'git rm ' +hint: and commit the result with 'git commit' +""" + CherryPicker myCherryPicker + GitLightRepository myRepository + GitLightRepository.Commit myInitialCommit + + static final LOCAL_CHANGES_OVERWRITTEN_BY_CHERRY_PICK = + """ + error: Your local changes to the following files would be overwritten by merge: + \ttest.txt + Please, commit your changes or stash them before you can merge. + Aborting + """; + + static final UNTRACKED_FILES_OVERWRITTEN_BY_CHERRY_PICK = + """ + error: The following untracked working tree files would be overwritten by merge: + \tcp.txt + Please move or remove them before you can merge. + Aborting + """ + + void setUp() { + super.setUp() + + myRepository = new GitLightRepository() + myRepositoryManager.add(myRepository) + myInitialCommit = myRepository.commit("initial") + } + + GitCommit commit(String commitMessage = "plain commit") { + AbstractHash hash = AbstractHash.create(Integer.toHexString(new SHA().hashCode())) + List changes = new ArrayList(); + changes.add(new Change(null, new MockContentRevision(new FilePathImpl(new MockVirtualFile("name")), VcsRevisionNumber.NULL))); + new GitCommit(hash, SHAHash.emulate(hash), "John Smith", null, null, commitMessage, commitMessage, null, null, null, null, null, null, + null, changes, 0) + } + + void assertOnlyDefaultChangelist() { + assertChangeLists( [DEFAULT], DEFAULT) + } + + void invokeCherryPick(GitCommit commit) { + invokeCherryPick([commit]) + } + + void invokeCherryPick(List commits) { + myCherryPicker.cherryPick(Collections.> singletonMap(myRepository, commits)) + } + + void assertHeadCommit(GitCommit commit) { + assertEquals "Wrong commit at the HEAD", commitMessageForCherryPick(commit), myRepository.head.commitMessage + } + + void assertLastCommits(GitCommit... commits) { + GitLightRepository.Commit current = myRepository.head + int level = 0; + for (GitCommit commit : commits) { + assertEquals "Wrong commit at level $level", commitMessageForCherryPick(commit), current.commitMessage + current = current.parent + level++; + } + } + + void assertChangeLists(Collection changeLists, String activeChangelist) { + ChangeListManager changeListManager = myPlatformFacade.getChangeListManager(myProject) + List lists = changeListManager.changeLists + Collection listNames = lists.collect { it.name } + assertEquals "Change lists are different", changeLists.toSet(), listNames.toSet() + assertEquals "Wrong active changelist", activeChangelist, changeListManager.defaultChangeList.name + } + + String commitDetails(GitCommit commit) { + "${commit.shortHash.toString()} \"${commit.subject}\"" + } + + String notificationContent(GitCommit... commits) { + commits.collect { commitDetails(it) }.join("
") + } + + void assertNotCherryPicked() { + // 1. assert not committed (i.e. git cherry-pick was not performed) + assertNothingCommitted() + // 2. assert working tree not changed (i.e. git cherry-pick -n was not performed either) + assertTrue myPlatformFacade.getChangeListManager(myProject).getAllChanges().isEmpty() + } + + void assertNothingCommitted() { + assertEquals(myInitialCommit, myRepository.head) + } + + void prepareConflict() { + myGit.registerOperationExecutors(new MockGit.SimpleErrorOperationExecutor(CHERRY_PICK, CHERRY_PICK_CONFLICT), + new MockGit.SimpleSuccessOperationExecutor(MockGit.OperationName.GET_UNMERGED_FILES, UNMERGED_FILE)) + } + + void assertMergeDialogShown() { + assertTrue "Merge dialog was not shown", myVcsHelper.mergeDialogWasShown() + } + + String newCommitMessage(GitCommit commit) { + "${commit.description}\n(cherry-picked from ${commit.hash.value})" + } + + protected static class OKCommitDialogHandler implements MockVcsHelper.CommitHandler { + + private final GitLightRepository myRepository; + boolean myCommitDialogShown + + OKCommitDialogHandler(GitLightRepository repository) { + myRepository = repository + } + + @Override + boolean commit(String commitMessage) { + myCommitDialogShown = true; + myRepository.commit(commitMessage) // answering OK in the dialog => committing + return true; + } + + boolean wasCommitDialogShown() { + myCommitDialogShown + } + } + + protected static class CancelCommitDialogHandler implements MockVcsHelper.CommitHandler { + + boolean myCommitDialogShown + + @Override + boolean commit(String commitMessage) { + myCommitDialogShown = true; + return false; + } + + boolean wasCommitDialogShown() { + myCommitDialogShown + } + } + +} diff --git a/plugins/git4idea/tests/git4idea/cherrypick/GitNotCommittingCherryPickTest.groovy b/plugins/git4idea/tests/git4idea/cherrypick/GitNotCommittingCherryPickTest.groovy new file mode 100644 index 000000000000..556bcaa29a68 --- /dev/null +++ b/plugins/git4idea/tests/git4idea/cherrypick/GitNotCommittingCherryPickTest.groovy @@ -0,0 +1,152 @@ +/* + * Copyright 2000-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package git4idea.cherrypick + +import com.intellij.notification.NotificationType +import git4idea.history.browser.CherryPicker +import git4idea.history.browser.GitCommit +import git4idea.test.MockGit +import git4idea.test.MockVcsHelper +import org.junit.Before +import org.junit.Test + +import static git4idea.test.MockGit.OperationName.CHERRY_PICK +import static junit.framework.Assert.* +import git4idea.test.GitLightRepository + +/** + * Tests for {@link CherryPicker}, when the "auto-commit on cherry-pick" option is deselected. + * Most situations are equal or similar, so the majority of cherry pick tests are located in {@link GitAutoCommittingCherryPickTest}. + * + * @author Kirill Likhodedov + */ +class GitNotCommittingCherryPickTest extends GitCherryPickTest { + + @Before + void setUp() { + super.setUp() + myCherryPicker = new CherryPicker(myProject, myGit, myPlatformFacade, false) + } + + @Test + void "clean tree, no conflicts, then show commit dialog, commit on ok"() { + GitCommit commit = commit() + + myGit.registerOperationExecutors(new MockGit.SuccessfulCherryPickExecutor(myRepository, commit)) + OKCommitDialogHandler handler = new OKCommitDialogHandler(myRepository) + myVcsHelper.registerHandler(handler) + + invokeCherryPick(commit) + + assertHeadCommit(commit) + assertOnlyDefaultChangelist() + assertTrue "Commit dialog was not shown", handler.wasCommitDialogShown() + // notification is shown from the successful commit, can't check from here + } + + @Test + void "dirty tree, conflicting with commit, then show error"() { + myGit.registerOperationExecutors(new MockGit.SimpleErrorOperationExecutor(CHERRY_PICK, LOCAL_CHANGES_OVERWRITTEN_BY_CHERRY_PICK)) + + def commit = commit() + OKCommitDialogHandler handler = new OKCommitDialogHandler(myRepository) + myVcsHelper.registerHandler(handler) + + invokeCherryPick(commit) + + assertNotCherryPicked() + assertFalse "Commit dialog was shown, but it shouldn't", handler.wasCommitDialogShown() + assertOnlyDefaultChangelist() + assertNotificationShown("Cherry-pick failed", + """ + ${commitDetails(commit)}
+ Your local changes would be overwritten by cherry-pick.
+ Commit your changes or stash them to proceed. + """, + NotificationType.ERROR) + } + + @Test + void "conflict, merge ok, commit cancelled, then new & active changelist"() { + prepareConflict() + + CancelCommitDialogHandler handler = new CancelCommitDialogHandler() + myVcsHelper.registerHandler(handler) + + GitCommit commit = commit() + invokeCherryPick(commit) + assertMergeDialogShown() + assertTrue "Commit dialog was not shown", handler.wasCommitDialogShown() + assertChangeLists([DEFAULT, newCommitMessage(commit)], newCommitMessage(commit)) + } + + @Test + void "2 simple commits in a row, then 2 commit dialogs in a row"() { + GitCommit commit1 = commit() + GitCommit commit2 = commit() + + myGit.registerOperationExecutors(new MockGit.SimpleSuccessOperationExecutor(CHERRY_PICK, ""), + new MockGit.SimpleSuccessOperationExecutor(CHERRY_PICK, "")) + + CountingOKCommitHandler handler = new CountingOKCommitHandler(myRepository) + myVcsHelper.registerHandler(handler) + + invokeCherryPick([commit1, commit2]) + + assertOnlyDefaultChangelist() + assertEquals "Commit dialog shown wrong number of times", 2, handler.myCommitDialogs + assertLastCommits(commit2, commit1) + } + + @Test + void "3 commits, 2nd conflicts with committed, then 1st success, on 2nd show merge dialog"() { + GitCommit commit1 = commit("First") + GitCommit commit2 = commit("Second") + GitCommit commit3 = commit("Third") + + myGit.registerOperationExecutors(new MockGit.SimpleSuccessOperationExecutor(CHERRY_PICK, "")) + prepareConflict() + myGit.registerOperationExecutors(new MockGit.SimpleSuccessOperationExecutor(CHERRY_PICK, "")) + + CountingOKCommitHandler handler = new CountingOKCommitHandler(myRepository) + myVcsHelper.registerHandler(handler) + + invokeCherryPick([commit1, commit2, commit3]) + + assertMergeDialogShown() + assertEquals "Commit dialog shown wrong number of times", 3, handler.myCommitDialogs + assertLastCommits commit3, commit2, commit1 + } + + private static class CountingOKCommitHandler implements MockVcsHelper.CommitHandler { + + GitLightRepository myRepository + int myCommitDialogs; + + CountingOKCommitHandler(GitLightRepository repository) { + myRepository = repository + } + + @Override + boolean commit(String commitMessage) { + myCommitDialogs++ + myRepository.commit(commitMessage) + return true; + } + + } + +} diff --git a/plugins/git4idea/tests/git4idea/repo/GitRepositoryTest.java b/plugins/git4idea/tests/git4idea/repo/GitRepositoryTest.java index 95da8f264fcd..0f308f8e7e66 100644 --- a/plugins/git4idea/tests/git4idea/repo/GitRepositoryTest.java +++ b/plugins/git4idea/tests/git4idea/repo/GitRepositoryTest.java @@ -48,7 +48,7 @@ public class GitRepositoryTest extends GitTest { @BeforeMethod public void setUp(Method testMethod) throws Exception { super.setUp(testMethod); - myRepository = GitRepository.getFullInstance(myRepo.getVFRootDir(), myProject, myProject); + myRepository = GitRepositoryImpl.getFullInstance(myRepo.getVFRootDir(), myProject, myProject); myReader = new GitRepositoryReader(new File(VfsUtil.virtualToIoFile(myRepository.getRoot()), ".git")); } diff --git a/plugins/git4idea/tests/git4idea/test/GitExec.java b/plugins/git4idea/tests/git4idea/test/GitExec.java index de61dc575ee3..08d063685281 100644 --- a/plugins/git4idea/tests/git4idea/test/GitExec.java +++ b/plugins/git4idea/tests/git4idea/test/GitExec.java @@ -23,6 +23,7 @@ import com.intellij.testFramework.VfsTestUtil; import com.intellij.util.ArrayUtil; import com.intellij.util.ui.UIUtil; import git4idea.repo.GitRepository; +import git4idea.repo.GitRepositoryImpl; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -41,7 +42,7 @@ public class GitExec { public static GitRepository init(@NotNull Project project, @NotNull VirtualFile root) throws IOException { new GitTestRunEnv(new File(root.getPath())).run("init"); root.refresh(false, true); - return GitRepository.getLightInstance(root, project, project); + return GitRepositoryImpl.getLightInstance(root, project, project); } /** @@ -57,7 +58,7 @@ public class GitExec { VirtualFile root = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(new File(destinationPath)); assert root != null; root.refresh(false, true); - return bare ? null : GitRepository.getLightInstance(root, project, project); + return bare ? null : GitRepositoryImpl.getLightInstance(root, project, project); } public static String push(@NotNull GitRepository repository, String... args) throws IOException { diff --git a/plugins/git4idea/tests/git4idea/test/GitFastTest.groovy b/plugins/git4idea/tests/git4idea/test/GitFastTest.groovy index d86ce71349d1..42b05a9764cd 100644 --- a/plugins/git4idea/tests/git4idea/test/GitFastTest.groovy +++ b/plugins/git4idea/tests/git4idea/test/GitFastTest.groovy @@ -16,14 +16,14 @@ package git4idea.test import com.intellij.notification.Notification - -import static git4idea.test.GitGTestUtil.stripLineBreaksAndHtml -import static junit.framework.Assert.assertEquals -import static junit.framework.Assert.assertNotNull +import com.intellij.notification.NotificationType import com.intellij.openapi.project.Project +import com.intellij.openapi.util.io.FileUtil import git4idea.tests.TestDialogManager import org.junit.Before -import com.intellij.openapi.util.io.FileUtil + +import static junit.framework.Assert.assertEquals +import static junit.framework.Assert.assertNotNull /** * @@ -31,11 +31,15 @@ import com.intellij.openapi.util.io.FileUtil */ class GitFastTest { + public static final String TEST_NOTIFICATION_GROUP = "Test" + Project myProject GitTestPlatformFacade myPlatformFacade + GitTestRepositoryManager myRepositoryManager MockGit myGit String myProjectDir TestDialogManager myDialogManager + MockVcsHelper myVcsHelper @Before void setUp() { @@ -48,14 +52,30 @@ class GitFastTest { myPlatformFacade = new GitTestPlatformFacade() myGit = new MockGit() myDialogManager = myPlatformFacade.getDialogManager() + myRepositoryManager = (GitTestRepositoryManager) myPlatformFacade.getRepositoryManager(myProject) + myVcsHelper = (MockVcsHelper) myPlatformFacade.getVcsHelper(myProject) } void assertNotificationShown(Notification expected) { if (expected) { Notification actualNotification = (myPlatformFacade.getNotificator(myProject) as TestNotificator).lastNotification assertNotNull "No notification was shown", actualNotification - assertEquals(stripLineBreaksAndHtml(expected.content), stripLineBreaksAndHtml(actualNotification.content)) + assertEquals "Notification has wrong title", expected.title, actualNotification.title + assertEquals "Notification has wrong type", expected.type, actualNotification.type + assertEquals "Notification has wrong content", adjustTestContent(expected.content), actualNotification.content } } + // we allow more spaces and line breaks in tests to make them more readable. + // After all, notifications display html, so all line breaks and extra spaces are ignored. + String adjustTestContent(String s) { + StringBuilder res = new StringBuilder() + s.split("\n").each { res.append it.trim() } + res.toString() + } + + void assertNotificationShown(String title, String message, NotificationType type) { + assertNotificationShown(new Notification(TEST_NOTIFICATION_GROUP, title, message, type)) + } + } diff --git a/plugins/git4idea/tests/git4idea/test/GitGTestUtil.groovy b/plugins/git4idea/tests/git4idea/test/GitGTestUtil.groovy index 24f285a34146..70dbd0ac9144 100644 --- a/plugins/git4idea/tests/git4idea/test/GitGTestUtil.groovy +++ b/plugins/git4idea/tests/git4idea/test/GitGTestUtil.groovy @@ -37,4 +37,8 @@ class GitGTestUtil { StringUtil.stripHtml(s, true).replace('\n', ''); } + static String stripLineBreaksAndMultiSpaces(String s) { + s.replace('\n', '').replaceAll(" {3,}", " ") + } + } diff --git a/plugins/git4idea/tests/git4idea/test/GitLightRepository.java b/plugins/git4idea/tests/git4idea/test/GitLightRepository.java new file mode 100644 index 000000000000..cd7013becb59 --- /dev/null +++ b/plugins/git4idea/tests/git4idea/test/GitLightRepository.java @@ -0,0 +1,214 @@ +/* + * Copyright 2000-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package git4idea.test; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.VirtualFile; +import git4idea.GitBranch; +import git4idea.branch.GitBranchesCollection; +import git4idea.repo.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import sun.security.provider.SHA; + +import java.util.Collection; + +/** + * Simulates Git repository. + * Made for unit tests, not to spawn a Git process. + * Stores information about commits and branches in memory. + * + * @author Kirill Likhodedov + */ +public class GitLightRepository implements GitRepository { + + public static class Commit { + @Nullable private Commit myParent; + @NotNull private String myHash; + @NotNull private String myCommitMessage; + + public Commit(String hash, String message, Commit parent) { + myHash = hash; + myCommitMessage = message; + myParent = parent; + } + + @NotNull + public String getCommitMessage() { + return myCommitMessage; + } + + @Nullable + public Commit getParent() { + return myParent; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Commit commit = (Commit)o; + + if (!myCommitMessage.equals(commit.myCommitMessage)) return false; + if (!myHash.equals(commit.myHash)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = myHash.hashCode(); + result = 31 * result + myCommitMessage.hashCode(); + return result; + } + + } + + private static class Branch { + @NotNull private String myName; + @Nullable private Commit myCommit; + + public Branch(String name, Commit commit) { + myName = name; + myCommit = commit; + } + } + + private Branch myHead; + + public GitLightRepository() { + myHead = new Branch("master", null); + } + + public Commit commit(String commitMessage) { + Commit parent = myHead.myCommit; + String hash = new SHA().toString(); + Commit commit = new Commit(hash, commitMessage, parent); + myHead.myCommit = commit; + return commit; + } + + public Commit cherryPick(String commitMessage) { + return commit(commitMessage); + } + + @Nullable + public Commit getHead() { + return myHead.myCommit; + } + + @NotNull + @Override + public VirtualFile getRoot() { + return new GitMockVirtualFile(FileUtil.getTempDirectory()); + } + + @NotNull + @Override + public VirtualFile getGitDir() { + return new GitMockVirtualFile(getRoot().getPath() + "/.git"); + } + + @NotNull + @Override + public String getPresentableUrl() { + throw new UnsupportedOperationException(); + } + + @NotNull + @Override + public Project getProject() { + throw new UnsupportedOperationException(); + } + + @NotNull + @Override + public GitUntrackedFilesHolder getUntrackedFilesHolder() { + throw new UnsupportedOperationException(); + } + + @NotNull + @Override + public State getState() { + throw new UnsupportedOperationException(); + } + + @Override + public String getCurrentRevision() { + throw new UnsupportedOperationException(); + } + + @Override + public GitBranch getCurrentBranch() { + throw new UnsupportedOperationException(); + } + + @NotNull + @Override + public GitBranchesCollection getBranches() { + throw new UnsupportedOperationException(); + } + + @NotNull + @Override + public GitConfig getConfig() { + throw new UnsupportedOperationException(); + } + + @NotNull + @Override + public Collection getRemotes() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isMergeInProgress() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isRebaseInProgress() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isOnBranch() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isFresh() { + throw new UnsupportedOperationException(); + } + + @Override + public void addListener(@NotNull GitRepositoryChangeListener listener) { + throw new UnsupportedOperationException(); + } + + @Override + public void update(TrackedTopic... topics) { + throw new UnsupportedOperationException(); + } + + @Override + public String toLogString() { + throw new UnsupportedOperationException(); + } + +} diff --git a/plugins/git4idea/tests/git4idea/test/GitMockProject.groovy b/plugins/git4idea/tests/git4idea/test/GitMockProject.groovy index d9455e20852d..2a042bdae16d 100644 --- a/plugins/git4idea/tests/git4idea/test/GitMockProject.groovy +++ b/plugins/git4idea/tests/git4idea/test/GitMockProject.groovy @@ -23,6 +23,7 @@ import com.intellij.util.messages.MessageBus import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.util.Condition import com.intellij.openapi.util.Key +import org.jetbrains.annotations.NotNull /** * @@ -36,6 +37,7 @@ class GitMockProject implements Project { myProjectDir = projectDir } + @NotNull @Override String getName() { throw new UnsupportedOperationException() @@ -56,6 +58,7 @@ class GitMockProject implements Project { throw new UnsupportedOperationException() } + @NotNull @Override String getProjectFilePath() { throw new UnsupportedOperationException() @@ -71,6 +74,7 @@ class GitMockProject implements Project { throw new UnsupportedOperationException() } + @NotNull @Override String getLocationHash() { throw new UnsupportedOperationException() diff --git a/plugins/git4idea/tests/git4idea/test/GitTestPlatformFacade.java b/plugins/git4idea/tests/git4idea/test/GitTestPlatformFacade.java index b7cfab32f417..c3364b3e8bb7 100644 --- a/plugins/git4idea/tests/git4idea/test/GitTestPlatformFacade.java +++ b/plugins/git4idea/tests/git4idea/test/GitTestPlatformFacade.java @@ -15,20 +15,23 @@ */ package git4idea.test; +import com.intellij.mock.MockLocalFileSystem; +import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.vcs.AbstractVcs; -import com.intellij.openapi.vcs.ProjectLevelVcsManager; -import git4idea.Notificator; -import git4idea.PlatformFacade; -import git4idea.tests.TestDialogManager; -import org.jetbrains.annotations.NotNull; import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.util.Computable; -import git4idea.config.GitVcsSettings; +import com.intellij.openapi.vcs.AbstractVcs; +import com.intellij.openapi.vcs.AbstractVcsHelper; +import com.intellij.openapi.vcs.ProjectLevelVcsManager; import com.intellij.openapi.vcs.changes.ChangeListManager; import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.mock.MockLocalFileSystem; +import com.intellij.testFramework.vcs.MockChangeListManager; +import git4idea.Notificator; +import git4idea.PlatformFacade; +import git4idea.repo.GitRepositoryManager; +import git4idea.tests.TestDialogManager; +import org.jetbrains.annotations.NotNull; /** * @@ -41,10 +44,15 @@ public class GitTestPlatformFacade implements PlatformFacade { private TestNotificator myNotificator; private TestDialogManager myTestDialogManager; private GitMockProjectRootManager myProjectRootManager; + private ChangeListManager myChangeListManager; + private GitTestRepositoryManager myRepositoryManager; + private MockVcsHelper myVcsHelper; public GitTestPlatformFacade() { myTestDialogManager = new TestDialogManager(); myProjectRootManager = new GitMockProjectRootManager(); + myChangeListManager = new MockChangeListManager(); + myRepositoryManager = new GitTestRepositoryManager(); } @NotNull @@ -95,13 +103,18 @@ public class GitTestPlatformFacade implements PlatformFacade { } @Override - public GitVcsSettings getGitWorkspaceSettings(@NotNull Project project) { - throw new UnsupportedOperationException(); + public void runWriteAction(@NotNull Runnable runnable) { + runnable.run(); + } + + @Override + public void invokeAndWait(@NotNull Runnable runnable, @NotNull ModalityState modalityState) { + runnable.run(); } @Override public ChangeListManager getChangeListManager(@NotNull Project project) { - throw new UnsupportedOperationException(); + return myChangeListManager; } @Override @@ -109,6 +122,21 @@ public class GitTestPlatformFacade implements PlatformFacade { return new MockLocalFileSystem(); } + @NotNull + @Override + public AbstractVcsHelper getVcsHelper(@NotNull Project project) { + if (myVcsHelper == null) { + myVcsHelper = new MockVcsHelper(); + } + return myVcsHelper; + } + + @NotNull + @Override + public GitRepositoryManager getRepositoryManager(@NotNull Project project) { + return myRepositoryManager; + } + @NotNull @Override public AbstractVcs getVcs(@NotNull Project project) { diff --git a/plugins/git4idea/tests/git4idea/test/GitTestRepositoryManager.java b/plugins/git4idea/tests/git4idea/test/GitTestRepositoryManager.java new file mode 100644 index 000000000000..4d4190a7bcd2 --- /dev/null +++ b/plugins/git4idea/tests/git4idea/test/GitTestRepositoryManager.java @@ -0,0 +1,75 @@ +/* + * Copyright 2000-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package git4idea.test; + +import com.intellij.openapi.vfs.VirtualFile; +import git4idea.repo.GitRepository; +import git4idea.repo.GitRepositoryChangeListener; +import git4idea.repo.GitRepositoryManager; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * @author Kirill Likhodedov + */ +class GitTestRepositoryManager implements GitRepositoryManager { + + private final Collection myRepositories = new ArrayList(); + + public void add(GitRepository repository) { + myRepositories.add(repository); + } + + @Override + public GitRepository getRepositoryForRoot(@Nullable VirtualFile root) { + return myRepositories.iterator().next(); + } + + @Override + public GitRepository getRepositoryForFile(@NotNull VirtualFile file) { + throw new UnsupportedOperationException(); + } + + @NotNull + @Override + public List getRepositories() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean moreThanOneRoot() { + throw new UnsupportedOperationException(); + } + + @Override + public void addListenerToAllRepositories(@NotNull GitRepositoryChangeListener listener) { + throw new UnsupportedOperationException(); + } + + @Override + public void updateRepository(VirtualFile root, GitRepository.TrackedTopic... topics) { + throw new UnsupportedOperationException(); + } + + @Override + public void updateAllRepositories(GitRepository.TrackedTopic... topics) { + throw new UnsupportedOperationException(); + } +} diff --git a/plugins/git4idea/tests/git4idea/test/MockGit.groovy b/plugins/git4idea/tests/git4idea/test/MockGit.groovy index fb6497932119..1bfae7b0802b 100644 --- a/plugins/git4idea/tests/git4idea/test/MockGit.groovy +++ b/plugins/git4idea/tests/git4idea/test/MockGit.groovy @@ -15,14 +15,21 @@ */ package git4idea.test +import com.intellij.execution.process.ProcessOutputTypes import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile +import com.intellij.util.containers.hash.HashMap import git4idea.commands.Git import git4idea.commands.GitCommandResult import git4idea.commands.GitLineHandlerListener +import git4idea.history.browser.GitCommit import git4idea.push.GitPushSpec import git4idea.repo.GitRepository import org.jetbrains.annotations.NotNull +import org.jetbrains.annotations.Nullable + +import static git4idea.test.MockGit.OperationName.CHERRY_PICK +import static git4idea.test.MockGit.OperationName.GET_UNMERGED_FILES /** * @@ -30,84 +37,240 @@ import org.jetbrains.annotations.NotNull */ class MockGit implements Git { + public static final GitCommandResult FAKE_SUCCESS_RESULT = new GitCommandResult(true, 0, Collections.emptyList(), Collections.emptyList()) + private final Map> myExecutors = new HashMap>() + + public static interface OperationExecutor { + GitCommandResult execute(); + OperationName getName(); + } + + public enum OperationName { + CHERRY_PICK, + GET_UNMERGED_FILES; + } + + /** + * Register executors for specific operations. These are put into queues, i.e. once operation is called, the executor is popped out of the + * queue. If the queue is empty or certain operation, then it is executed as by default. + */ + void registerOperationExecutors(OperationExecutor... executors) { + for (OperationExecutor executor : executors) { + OperationName name = executor.getName() + Queue exs = myExecutors.get(name) + if (exs == null) { + exs = new ArrayDeque() + myExecutors.put(name, exs) + } + exs.add(executor) + } + } + + @NotNull @Override GitCommandResult init(@NotNull Project project, @NotNull VirtualFile root, @NotNull GitLineHandlerListener... listeners) { new File(root.path, ".git").mkdir() - new GitCommandResult(true, 0, Collections.emptyList(), Collections.emptyList()) + FAKE_SUCCESS_RESULT } + @NotNull @Override - Set untrackedFiles(Project project, VirtualFile root, Collection files) { + Set untrackedFiles(@NotNull Project project, @NotNull VirtualFile root, Collection files) { throw new UnsupportedOperationException() } + @NotNull @Override - Collection untrackedFilesNoChunk(Project project, VirtualFile root, List relativePaths) { + Collection untrackedFilesNoChunk(@NotNull Project project, @NotNull VirtualFile root, List relativePaths) { throw new UnsupportedOperationException() } + @NotNull @Override - GitCommandResult clone(Project project, File parentDirectory, String url, String clonedDirectoryName) { + GitCommandResult clone(@NotNull Project project, @NotNull File parentDirectory, @NotNull String url, @NotNull String clonedDirectoryName) { throw new UnsupportedOperationException() } + @NotNull @Override - GitCommandResult merge(GitRepository repository, String branchToMerge, GitLineHandlerListener... listeners) { + GitCommandResult merge(@NotNull GitRepository repository, @NotNull String branchToMerge, @NotNull GitLineHandlerListener... listeners) { throw new UnsupportedOperationException() } + @NotNull @Override - GitCommandResult checkout(GitRepository repository, String reference, String newBranch, boolean force, GitLineHandlerListener... listeners) { + GitCommandResult checkout(@NotNull GitRepository repository, @NotNull String reference, String newBranch, boolean force, @NotNull GitLineHandlerListener... listeners) { throw new UnsupportedOperationException() } + @NotNull @Override - GitCommandResult checkoutNewBranch(GitRepository repository, String branchName, GitLineHandlerListener listener) { + GitCommandResult checkoutNewBranch(@NotNull GitRepository repository, @NotNull String branchName, GitLineHandlerListener listener) { throw new UnsupportedOperationException() } + @NotNull @Override - GitCommandResult createNewTag(GitRepository repository, String tagName, GitLineHandlerListener listener, String reference) { + GitCommandResult createNewTag(@NotNull GitRepository repository, @NotNull String tagName, GitLineHandlerListener listener, @NotNull String reference) { throw new UnsupportedOperationException() } + @NotNull @Override - GitCommandResult branchDelete(GitRepository repository, String branchName, boolean force, GitLineHandlerListener... listeners) { + GitCommandResult branchDelete(@NotNull GitRepository repository, @NotNull String branchName, boolean force, @NotNull GitLineHandlerListener... listeners) { throw new UnsupportedOperationException() } + @NotNull @Override - GitCommandResult branchContains(GitRepository repository, String commit) { + GitCommandResult branchContains(@NotNull GitRepository repository, @NotNull String commit) { throw new UnsupportedOperationException() } + @NotNull @Override - GitCommandResult branchCreate(GitRepository repository, String branchName) { + GitCommandResult branchCreate(@NotNull GitRepository repository, @NotNull String branchName) { throw new UnsupportedOperationException() } + @NotNull @Override - GitCommandResult resetHard(GitRepository repository, String revision) { + GitCommandResult resetHard(@NotNull GitRepository repository, @NotNull String revision) { throw new UnsupportedOperationException() } + @NotNull @Override - GitCommandResult resetMerge(GitRepository repository, String revision) { + GitCommandResult resetMerge(@NotNull GitRepository repository, String revision) { throw new UnsupportedOperationException() } + @NotNull @Override - GitCommandResult tip(GitRepository repository, String branchName) { + GitCommandResult tip(@NotNull GitRepository repository, @NotNull String branchName) { throw new UnsupportedOperationException() } + @NotNull @Override - GitCommandResult push(GitRepository repository, String remote, String spec, GitLineHandlerListener... listeners) { + GitCommandResult push(@NotNull GitRepository repository, @NotNull String remote, @NotNull String spec, @NotNull GitLineHandlerListener... listeners) { throw new UnsupportedOperationException() } + @NotNull @Override - GitCommandResult push(GitRepository repository, GitPushSpec pushSpec, GitLineHandlerListener... listeners) { + GitCommandResult push(@NotNull GitRepository repository, @NotNull GitPushSpec pushSpec, @NotNull GitLineHandlerListener... listeners) { throw new UnsupportedOperationException() } + + @NotNull + @Override + GitCommandResult cherryPick(@NotNull GitRepository repository, @NotNull String hash, boolean autoCommit, @NotNull GitLineHandlerListener... listeners) { + GitCommandResult result = callExecutor(CHERRY_PICK) + if (result != null) { + produceOutput(result.getOutputAsJoinedString(), listeners) + return result; + } + ((GitLightRepository)repository).cherryPick("cherry-pick from $hash") + return FAKE_SUCCESS_RESULT + } + + @NotNull + @Override + GitCommandResult getUnmergedFiles(@NotNull GitRepository repository) { + GitCommandResult result = callExecutor(GET_UNMERGED_FILES) + if (result != null) { + return result; + } + return FAKE_SUCCESS_RESULT + } + + private void produceOutput(String output, GitLineHandlerListener... listeners) { + for (String line : output.split("\n")) { // for simplicity all output goes to OUTPUT, no ERROR + listeners.each { it.onLineAvailable(line, ProcessOutputTypes.STDOUT) } + } + } + + @Nullable + private GitCommandResult callExecutor(OperationName operationName) { + Queue cherryPickExecutors = myExecutors.get(operationName) + if (cherryPickExecutors != null && !cherryPickExecutors.isEmpty()) { + OperationExecutor executor = cherryPickExecutors.poll() + return executor.execute() + } + return null; + } + + public static class SimpleErrorOperationExecutor implements MockGit.OperationExecutor { + + String myOutput + MockGit.OperationName myOperationName + + SimpleErrorOperationExecutor(MockGit.OperationName operationName, String output) { + myOutput = output; + myOperationName = operationName + } + + @Override + GitCommandResult execute() { + return result(myOutput, false); + } + + @Override + MockGit.OperationName getName() { + return myOperationName + } + } + + public static class SimpleSuccessOperationExecutor implements MockGit.OperationExecutor { + + String myOutput + MockGit.OperationName myOperationName + + SimpleSuccessOperationExecutor(MockGit.OperationName operationName, String output) { + myOutput = output; + myOperationName = operationName + } + + @Override + GitCommandResult execute() { + return result(myOutput, true); + } + + @Override + MockGit.OperationName getName() { + return myOperationName + } + } + + private static GitCommandResult result(String output, boolean success) { + new GitCommandResult(success, success ? 0 : 127, Collections.emptyList(), Collections.singletonList(output)) + } + + public static class SuccessfulCherryPickExecutor implements OperationExecutor { + + GitRepository myRepository + GitCommit myOriginalCommit + + SuccessfulCherryPickExecutor(GitRepository repository, GitCommit originalCommit) { + myRepository = repository; + myOriginalCommit = originalCommit + } + + @Override + GitCommandResult execute() { + ((GitLightRepository)myRepository).cherryPick(commitMessageForCherryPick(myOriginalCommit)) + return FAKE_SUCCESS_RESULT + } + + @Override + OperationName getName() { + return CHERRY_PICK + } + } + + static String commitMessageForCherryPick(GitCommit commit) { + "$commit.subject\n(cherry-picked from ${commit.shortHash.getString()})" + } + } diff --git a/plugins/git4idea/tests/git4idea/test/MockVcsHelper.java b/plugins/git4idea/tests/git4idea/test/MockVcsHelper.java new file mode 100644 index 000000000000..3fa663253131 --- /dev/null +++ b/plugins/git4idea/tests/git4idea/test/MockVcsHelper.java @@ -0,0 +1,204 @@ +/* + * Copyright 2000-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package git4idea.test; + +import com.intellij.ide.errorTreeView.HotfixData; +import com.intellij.openapi.vcs.*; +import com.intellij.openapi.vcs.annotate.AnnotationProvider; +import com.intellij.openapi.vcs.annotate.FileAnnotation; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.CommitExecutor; +import com.intellij.openapi.vcs.changes.LocalChangeList; +import com.intellij.openapi.vcs.history.VcsFileRevision; +import com.intellij.openapi.vcs.history.VcsHistoryProvider; +import com.intellij.openapi.vcs.merge.MergeDialogCustomizer; +import com.intellij.openapi.vcs.merge.MergeProvider; +import com.intellij.openapi.vcs.versionBrowser.ChangeBrowserSettings; +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.awt.*; +import java.io.File; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * @author Kirill Likhodedov + */ +public class MockVcsHelper extends AbstractVcsHelper { + private boolean myMergeDialogShown; + private CommitHandler myCommitHandler; + + @Override + public void showErrors(List abstractVcsExceptions, @NotNull String tabDisplayName) { + throw new UnsupportedOperationException(); + } + + @Override + public void showErrors(Map> exceptionGroups, @NotNull String tabDisplayName) { + throw new UnsupportedOperationException(); + } + + @Override + public List runTransactionRunnable(AbstractVcs vcs, TransactionRunnable runnable, Object vcsParameters) { + throw new UnsupportedOperationException(); + } + + @Override + public void showAnnotation(FileAnnotation annotation, VirtualFile file, AbstractVcs vcs) { + throw new UnsupportedOperationException(); + } + + @Override + public void showDifferences(VcsFileRevision cvsVersionOn, VcsFileRevision cvsVersionOn1, File file) { + throw new UnsupportedOperationException(); + } + + @Override + public void showChangesListBrowser(CommittedChangeList changelist, @Nls String title) { + throw new UnsupportedOperationException(); + } + + @Override + public void showChangesBrowser(List changelists) { + throw new UnsupportedOperationException(); + } + + @Override + public void showChangesBrowser(List changelists, @Nls String title) { + throw new UnsupportedOperationException(); + } + + @Override + public void showChangesBrowser(CommittedChangesProvider provider, + RepositoryLocation location, + @Nls String title, + @Nullable Component parent) { + throw new UnsupportedOperationException(); + } + + @Override + public void showWhatDiffersBrowser(@Nullable Component parent, Collection changes, @Nls String title) { + throw new UnsupportedOperationException(); + } + + @Override + public T chooseCommittedChangeList(CommittedChangesProvider provider, + RepositoryLocation location) { + throw new UnsupportedOperationException(); + } + + @Override + public void openCommittedChangesTab(AbstractVcs vcs, VirtualFile root, ChangeBrowserSettings settings, int maxCount, String title) { + throw new UnsupportedOperationException(); + } + + @Override + public void openCommittedChangesTab(CommittedChangesProvider provider, + RepositoryLocation location, + ChangeBrowserSettings settings, + int maxCount, + String title) { + throw new UnsupportedOperationException(); + } + + @NotNull + @Override + public List showMergeDialog(List files, + MergeProvider provider, + @NotNull MergeDialogCustomizer mergeDialogCustomizer) { + myMergeDialogShown = true; + return Collections.emptyList(); + } + + public boolean mergeDialogWasShown() { + return myMergeDialogShown; + } + + @NotNull + @Override + public List showMergeDialog(List files, MergeProvider provider) { + return showMergeDialog(files, provider, new MergeDialogCustomizer()); + } + + @NotNull + @Override + public List showMergeDialog(List files) { + throw new UnsupportedOperationException(); + } + + @Override + public void showFileHistory(VcsHistoryProvider vcsHistoryProvider, FilePath path, AbstractVcs vcs, String repositoryPath) { + throw new UnsupportedOperationException(); + } + + @Override + public void showFileHistory(VcsHistoryProvider vcsHistoryProvider, + AnnotationProvider annotationProvider, + FilePath path, + String repositoryPath, + AbstractVcs vcs) { + throw new UnsupportedOperationException(); + } + + @Override + public void showRollbackChangesDialog(List changes) { + throw new UnsupportedOperationException(); + } + + @Override + public Collection selectFilesToProcess(List files, + String title, + @Nullable String prompt, + String singleFileTitle, + String singleFilePromptTemplate, + VcsShowConfirmationOption confirmationOption) { + throw new UnsupportedOperationException(); + } + + @Override + public Collection selectFilePathsToProcess(List files, + String title, + @Nullable String prompt, + String singleFileTitle, + String singleFilePromptTemplate, + VcsShowConfirmationOption confirmationOption) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean commitChanges(@NotNull List changes, @NotNull LocalChangeList initialChangeList, + @NotNull String commitMessage, @Nullable CommitExecutor executor) { + if (myCommitHandler != null) { + return myCommitHandler.commit(commitMessage); + } + return false; + } + + public void registerHandler(CommitHandler handler) { + myCommitHandler = handler; + } + + public interface CommitHandler { + boolean commit(String commitMessage); + } + +} diff --git a/plugins/git4idea/tests/git4idea/test/TestNotificator.java b/plugins/git4idea/tests/git4idea/test/TestNotificator.java index e4f6c6a6fdae..143d6f0d93c7 100644 --- a/plugins/git4idea/tests/git4idea/test/TestNotificator.java +++ b/plugins/git4idea/tests/git4idea/test/TestNotificator.java @@ -44,11 +44,26 @@ public class TestNotificator extends Notificator { notify(notificationGroup, title, message, type, null); } + @Override + public void notifyError(@NotNull String title, @NotNull String message, @Nullable NotificationListener listener) { + myLastNotification = createNotification(title, message, NotificationType.ERROR); + } + @Override public void notifySuccess(@NotNull String title, @NotNull String message) { myLastNotification = createNotification(title, message, NotificationType.INFORMATION); } + @Override + public void notifyWeakWarning(@NotNull String title, @NotNull String message, @Nullable NotificationListener listener) { + myLastNotification = createNotification(title, message, NotificationType.WARNING); + } + + @Override + public void notifyStrongWarning(@NotNull String title, @NotNull String content, @Nullable NotificationListener listener) { + myLastNotification = createNotification(title, content, NotificationType.WARNING); + } + @NotNull private static Notification createNotification(@NotNull String title, @NotNull String message, NotificationType type) { return new Notification(TEST_NOTIFICATION_GROUP, title, message, type); diff --git a/plugins/git4idea/tests/git4idea/tests/rebase/GitRebaserReorderCommitsTest.java b/plugins/git4idea/tests/git4idea/tests/rebase/GitRebaserReorderCommitsTest.java index 00277378863d..fe53f30e06c2 100644 --- a/plugins/git4idea/tests/git4idea/tests/rebase/GitRebaserReorderCommitsTest.java +++ b/plugins/git4idea/tests/git4idea/tests/rebase/GitRebaserReorderCommitsTest.java @@ -15,7 +15,9 @@ */ package git4idea.tests.rebase; +import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.vfs.VirtualFile; +import git4idea.commands.Git; import git4idea.rebase.GitRebaser; import git4idea.tests.GitTest; import org.testng.annotations.BeforeMethod; @@ -41,7 +43,7 @@ public class GitRebaserReorderCommitsTest extends GitTest { @BeforeMethod @Override protected void setUp(Method testMethod) throws Exception { super.setUp(testMethod); - myRebaser = new GitRebaser(myProject, null); + myRebaser = new GitRebaser(myProject, ServiceManager.getService(Git.class), null); myRoot = myRepo.getVFRootDir(); myFirstCommit = makeCommit(); } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleScriptType.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleScriptType.java index 2297c829b253..f10f3ab0505e 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleScriptType.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleScriptType.java @@ -60,6 +60,7 @@ import javax.swing.*; import java.io.File; import java.io.IOException; import java.util.Collection; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -97,10 +98,10 @@ public class GradleScriptType extends GroovyScriptType { configuration.setName(configuration.getName() + "." + target); } - final CompileStepBeforeRun.MakeBeforeRunTask runTask = - RunManagerEx.getInstanceEx(file.getProject()).getBeforeRunTask(configuration, CompileStepBeforeRun.ID); - if (runTask != null) { - runTask.setEnabled(false); + final List runTasks = + RunManagerEx.getInstanceEx(file.getProject()).getBeforeRunTasks(configuration, CompileStepBeforeRun.ID); + for (CompileStepBeforeRun.MakeBeforeRunTask task : runTasks) { + task.setEnabled(false); } } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/GroovyInspectionBundle.properties b/plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/GroovyInspectionBundle.properties index a2b7cb92f819..7039ad2ae445 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/GroovyInspectionBundle.properties +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/GroovyInspectionBundle.properties @@ -30,7 +30,7 @@ dialog.create.class.label.0=Create class ''{0}'' second.unsafe.call=Second unsafe call call.can.throw.npe=Calling chain can throw NullPointerException -groovy.dfa.issues=Data Flow Issues +groovy.dfa.issues=Data flow issues unused.assignment=Unused Assignment unused.assignment.tooltip=Assignment is not used diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/gant/GantScriptType.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/gant/GantScriptType.java index faac54dbab44..104a076eb411 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/gant/GantScriptType.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/gant/GantScriptType.java @@ -96,10 +96,10 @@ public class GantScriptType extends GroovyScriptType { configuration.setScriptParameters(target); configuration.setName(configuration.getName() + "." + target); } - final CompileStepBeforeRun.MakeBeforeRunTask runTask = - RunManagerEx.getInstanceEx(file.getProject()).getBeforeRunTask(configuration, CompileStepBeforeRun.ID); - if (runTask != null) { - runTask.setEnabled(false); + final List runTasks = + RunManagerEx.getInstanceEx(file.getProject()).getBeforeRunTasks(configuration, CompileStepBeforeRun.ID); + for (CompileStepBeforeRun.MakeBeforeRunTask task : runTasks) { + task.setEnabled(false); } } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/mvc/MvcFramework.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/mvc/MvcFramework.java index 4fdb8e73bc98..c30348f74347 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/mvc/MvcFramework.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/mvc/MvcFramework.java @@ -353,9 +353,9 @@ public abstract class MvcFramework { runManager.addConfiguration(runSettings, false); runManager.setActiveConfiguration(runSettings); - final CompileStepBeforeRun.MakeBeforeRunTask runTask = runManager.getBeforeRunTask(configuration, CompileStepBeforeRun.ID); - if (runTask != null) { - runTask.setEnabled(false); + final List runTasks = runManager.getBeforeRunTasks(configuration, CompileStepBeforeRun.ID); + for (CompileStepBeforeRun.MakeBeforeRunTask task : runTasks) { + task.setEnabled(false); } } diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/inspections/GroovyValidGroupNameTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/inspections/GroovyValidGroupNameTest.groovy new file mode 100644 index 000000000000..628daea0f6d0 --- /dev/null +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/inspections/GroovyValidGroupNameTest.groovy @@ -0,0 +1,31 @@ +/* + * Copyright 2000-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.plugins.groovy.inspections + +import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase +import org.jetbrains.plugins.groovy.codeInspection.unusedDef.UnusedDefInspection +import com.siyeh.ig.dataflow.UnnecessaryLocalVariableInspection + +/** + * @author Sergey Evdokimov + */ +class GroovyValidGroupNameTest extends LightCodeInsightFixtureTestCase { + + public void testGroupNamesIsSame() { + assert new UnusedDefInspection().getGroupDisplayName() == new UnnecessaryLocalVariableInspection().getGroupDisplayName() + } + +} diff --git a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgMockVcsHelper.java b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgMockVcsHelper.java index ccfa7538b3aa..ee4f0b92b2e3 100644 --- a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgMockVcsHelper.java +++ b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgMockVcsHelper.java @@ -20,6 +20,8 @@ import com.intellij.openapi.vcs.*; import com.intellij.openapi.vcs.annotate.AnnotationProvider; import com.intellij.openapi.vcs.annotate.FileAnnotation; import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.CommitExecutor; +import com.intellij.openapi.vcs.changes.LocalChangeList; import com.intellij.openapi.vcs.history.VcsFileRevision; import com.intellij.openapi.vcs.history.VcsHistoryProvider; import com.intellij.openapi.vcs.merge.MergeDialogCustomizer; @@ -168,6 +170,12 @@ public class HgMockVcsHelper extends AbstractVcsHelper { return null; } + @Override + public boolean commitChanges(@NotNull List changes, @NotNull LocalChangeList initialChangeList, + @NotNull String commitMessage, @Nullable CommitExecutor executor) { + throw new UnsupportedOperationException(); + } + public void addListener(VcsHelperListener listener) { myListeners.add(listener); } diff --git a/plugins/junit/src/com/intellij/execution/junit2/ui/Formatters.java b/plugins/junit/src/com/intellij/execution/junit2/ui/Formatters.java index 518374d32d9c..700563fd1233 100644 --- a/plugins/junit/src/com/intellij/execution/junit2/ui/Formatters.java +++ b/plugins/junit/src/com/intellij/execution/junit2/ui/Formatters.java @@ -33,9 +33,29 @@ public class Formatters { } public static String printTime(final long milliseconds) { - if (milliseconds == 0) + if (milliseconds == 0) { return ExecutionBundle.message("junit.runing.info.time.sec.message", "0.0"); - return ExecutionBundle.message("junit.runing.info.time.sec.message", NumberFormat.getInstance().format((double)milliseconds/1000.0)); + } + long seconds = milliseconds / 1000; + if (seconds == 0) { + return ExecutionBundle.message("junit.runing.info.time.sec.message", NumberFormat.getInstance().format((double)milliseconds/1000.0)); + } + + final StringBuilder sb = new StringBuilder(); + if (seconds >= 3600) { + sb.append(seconds / 3600).append("h "); + seconds %= 3600; + } + + if (seconds >= 60) { + sb.append(seconds / 60).append("m "); + seconds %= 60; + } + + if (seconds > 0 || sb.length() > 0) { + sb.append(seconds).append("s"); + } + return sb.toString(); } public static String printMemory(final long memory) { diff --git a/plugins/junit/src/com/intellij/execution/junit2/ui/model/CompletionEvent.java b/plugins/junit/src/com/intellij/execution/junit2/ui/model/CompletionEvent.java index e642a71e45e4..dedd974a8f15 100644 --- a/plugins/junit/src/com/intellij/execution/junit2/ui/model/CompletionEvent.java +++ b/plugins/junit/src/com/intellij/execution/junit2/ui/model/CompletionEvent.java @@ -17,7 +17,6 @@ package com.intellij.execution.junit2.ui.model; import com.intellij.execution.junit2.ui.Formatters; -import com.intellij.execution.ExecutionBundle; public class CompletionEvent extends StateEvent { private final boolean myNormalExit; diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/tasks/MavenBeforeRunTask.java b/plugins/maven/src/main/java/org/jetbrains/idea/maven/tasks/MavenBeforeRunTask.java index d6be660fba56..8a77eb8ac382 100644 --- a/plugins/maven/src/main/java/org/jetbrains/idea/maven/tasks/MavenBeforeRunTask.java +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/tasks/MavenBeforeRunTask.java @@ -20,16 +20,12 @@ import com.intellij.openapi.util.io.FileUtil; import org.jdom.Element; import org.jetbrains.idea.maven.project.MavenProject; -public class MavenBeforeRunTask extends BeforeRunTask { +public class MavenBeforeRunTask extends BeforeRunTask { private String myProjectPath; private String myGoal; public MavenBeforeRunTask() { - } - - public MavenBeforeRunTask(String projectPath, String goal) { - myProjectPath = projectPath; - myGoal = goal; + super(MavenBeforeRunTasksProvider.ID); } public String getProjectPath() { diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/tasks/MavenBeforeRunTasksProvider.java b/plugins/maven/src/main/java/org/jetbrains/idea/maven/tasks/MavenBeforeRunTasksProvider.java index 8d2d85409c13..1bf7c920fa8d 100644 --- a/plugins/maven/src/main/java/org/jetbrains/idea/maven/tasks/MavenBeforeRunTasksProvider.java +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/tasks/MavenBeforeRunTasksProvider.java @@ -36,12 +36,14 @@ import org.jetbrains.idea.maven.execution.MavenRunnerParameters; import org.jetbrains.idea.maven.navigator.SelectMavenGoalDialog; import org.jetbrains.idea.maven.project.MavenProject; import org.jetbrains.idea.maven.project.MavenProjectsManager; +import org.jetbrains.idea.maven.utils.MavenIcons; import org.jetbrains.idea.maven.utils.MavenLog; +import javax.swing.*; import java.util.Collections; public class MavenBeforeRunTasksProvider extends BeforeRunTaskProvider { - public static final Key TASK_ID = Key.create("Maven.BeforeRunTask"); + public static final Key ID = Key.create("Maven.BeforeRunTask"); private final Project myProject; public MavenBeforeRunTasksProvider(Project project) { @@ -49,10 +51,21 @@ public class MavenBeforeRunTasksProvider extends BeforeRunTaskProvider getId() { - return TASK_ID; + return ID; } - public String getDescription(RunConfiguration runConfiguration, MavenBeforeRunTask task) { + @Override + public String getName() { + return TasksBundle.message("maven.tasks.before.run.empty"); + } + + @Override + public Icon getIcon() { + return MavenIcons.MAVEN_ICON; + } + + @Override + public String getDescription(MavenBeforeRunTask task) { String desc = null; if (task.isEnabled()) { Pair projectAndGoal = getProjectAndGoalChecked(task); @@ -77,7 +90,7 @@ public class MavenBeforeRunTasksProvider extends BeforeRunTaskProvider getTaskID() { - return MavenBeforeRunTasksProvider.TASK_ID; + return MavenBeforeRunTasksProvider.ID; } @Override diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/tasks/actions/ToggleBeforeRunTaskAction.java b/plugins/maven/src/main/java/org/jetbrains/idea/maven/tasks/actions/ToggleBeforeRunTaskAction.java index d73fc3f02d71..62949c591b88 100644 --- a/plugins/maven/src/main/java/org/jetbrains/idea/maven/tasks/actions/ToggleBeforeRunTaskAction.java +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/tasks/actions/ToggleBeforeRunTaskAction.java @@ -40,7 +40,7 @@ public class ToggleBeforeRunTaskAction extends MavenToggleAction { final DataContext context = e.getDataContext(); final Pair desc = getTaskDesc(context); if (desc != null) { - for (MavenBeforeRunTask each : getRunManager(context).getBeforeRunTasks(MavenBeforeRunTasksProvider.TASK_ID, true)) { + for (MavenBeforeRunTask each : getRunManager(context).getBeforeRunTasks(MavenBeforeRunTasksProvider.ID, true)) { if (each.isEnabled() && each.isFor(desc.first, desc.second)) return true; } } diff --git a/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/componentTree/TreeContentProvider.java b/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/componentTree/TreeContentProvider.java index 0c30ca3c7b3e..d98e4900af48 100644 --- a/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/componentTree/TreeContentProvider.java +++ b/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/componentTree/TreeContentProvider.java @@ -19,7 +19,6 @@ import com.intellij.designer.designSurface.DesignerEditorPanel; import com.intellij.designer.model.RadComponent; import com.intellij.ide.util.treeView.AbstractTreeStructure; import com.intellij.ide.util.treeView.NodeDescriptor; -import com.intellij.util.ArrayUtil; import org.jetbrains.annotations.NotNull; /** @@ -43,13 +42,11 @@ public final class TreeContentProvider extends AbstractTreeStructure { if (element == myTreeRoot) { return myDesigner.getTreeRoots(); } - else if (element instanceof RadComponent) { + if (element instanceof RadComponent) { RadComponent component = (RadComponent)element; return component.getTreeChildren(); } - else { - throw new IllegalArgumentException("Unknown element: " + element); - } + throw new IllegalArgumentException("Unknown element: " + element); } @Override @@ -67,9 +64,7 @@ public final class TreeContentProvider extends AbstractTreeStructure { if (element == myTreeRoot || element instanceof RadComponent) { return new TreeNodeDescriptor(parentDescriptor, element); } - else { - throw new IllegalArgumentException("Unknown element: " + element); - } + throw new IllegalArgumentException("Unknown element: " + element); } @Override diff --git a/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/componentTree/TreeEditOperation.java b/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/componentTree/TreeEditOperation.java index 932f9d4ec08e..9bc36e53acd8 100644 --- a/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/componentTree/TreeEditOperation.java +++ b/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/componentTree/TreeEditOperation.java @@ -22,17 +22,23 @@ import com.intellij.designer.model.RadComponent; import org.jetbrains.annotations.Nullable; import java.awt.*; +import java.util.List; /** * @author Alexander Lobas */ public abstract class TreeEditOperation extends AbstractEditOperation { - protected final RadComponent myHost; + private RadComponent myTarget; + private boolean myInsertBefore; - public static boolean isTarget(RadComponent host, OperationContext context) { + public TreeEditOperation(RadComponent container, OperationContext context) { + super(container, context); + } + + public static boolean isTarget(RadComponent container, OperationContext context) { Point location = context.getLocation(); RadComponent target = context.getArea().findTarget(location.x, location.y, null); - if (target == host) { + if (target == container) { FeedbackTreeLayer layer = context.getArea().getFeedbackTreeLayer(); return !layer.isBeforeLocation(target, location.x, location.y) && !layer.isAfterLocation(target, location.x, location.y); @@ -40,32 +46,28 @@ public abstract class TreeEditOperation extends AbstractEditOperation { return true; } - public TreeEditOperation(RadComponent host, OperationContext context) { - super(context); - myHost = host; - } - @Override public void showFeedback() { Point location = myContext.getLocation(); - RadComponent target = myContext.getArea().findTarget(location.x, location.y, null); FeedbackTreeLayer layer = myContext.getArea().getFeedbackTreeLayer(); - if (myHost == target) { - layer.mark(target, FeedbackTreeLayer.INSERT_SELECTION); + myTarget = myContext.getArea().findTarget(location.x, location.y, null); + + if (myContainer == myTarget) { + layer.mark(myTarget, FeedbackTreeLayer.INSERT_SELECTION); } - else if (target != null && isChildren(target)) { - layer.mark(target, - layer.isBeforeLocation(target, location.x, location.y) ? - FeedbackTreeLayer.INSERT_BEFORE : FeedbackTreeLayer.INSERT_AFTER); + else if (myTarget != null && isChildren(myTarget)) { + myInsertBefore = layer.isBeforeLocation(myTarget, location.x, location.y); + layer.mark(myTarget, myInsertBefore ? FeedbackTreeLayer.INSERT_BEFORE : FeedbackTreeLayer.INSERT_AFTER); } else { + myTarget = null; eraseFeedback(); } } private boolean isChildren(RadComponent component) { - for (Object child : myHost.getTreeChildren()) { + for (Object child : myContainer.getTreeChildren()) { if (child == component) { return true; } @@ -80,54 +82,42 @@ public abstract class TreeEditOperation extends AbstractEditOperation { @Override public boolean canExecute() { - RadComponent reference = getReference(); - if (reference == null) { + if (myTarget == null) { return false; } - return canExecute(myHost == reference ? null : reference); - } + if (myContext.isMove() && myTarget != myContainer) { + if (myComponents.contains(myTarget)) { + return false; + } - protected boolean canExecute(RadComponent insertBefore) { + List children = myContainer.getChildren(); + int index = children.indexOf(myTarget) + (myInsertBefore ? -1 : 1); + if (0 <= index && index < children.size()) { + return !myComponents.contains(children.get(index)); + } + } return true; } @Override public void execute() throws Exception { - RadComponent reference = getReference(); - execute(myHost == reference ? null : reference); + if (myTarget == myContainer) { + execute(null); + } + else if (myInsertBefore) { + execute(myTarget); + } + else { + List children = myContainer.getChildren(); + int index = children.indexOf(myTarget) + 1; + if (index < children.size()) { + execute(children.get(index)); + } + else { + execute(null); + } + } } - protected abstract void execute(RadComponent insertBefore) throws Exception; - - @Nullable - private RadComponent getReference() { - Point location = myContext.getLocation(); - RadComponent target = myContext.getArea().findTarget(location.x, location.y, null); - - if (myHost == target) { - return myHost; - } - if (target != null) { - Object[] children = myHost.getTreeChildren(); - int index = -1; - - for (int i = 0; i < children.length; i++) { - if (children[i] == target) { - index = i + 1; - break; - } - } - if (index == -1) { - return null; - } - if (myContext.getArea().getFeedbackTreeLayer().isBeforeLocation(target, location.x, location.y)) { - return target; - } - if (index < children.length) { - return (RadComponent)children[index]; - } - return myHost; - } - return null; - } + protected abstract void execute(@Nullable RadComponent insertBefore) throws Exception; } \ No newline at end of file diff --git a/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/designSurface/AbstractEditOperation.java b/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/designSurface/AbstractEditOperation.java index 1ddc988c8c1a..0e8cc7872ab4 100644 --- a/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/designSurface/AbstractEditOperation.java +++ b/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/designSurface/AbstractEditOperation.java @@ -24,10 +24,12 @@ import java.util.List; * @author Alexander Lobas */ public abstract class AbstractEditOperation implements EditOperation { + protected final RadComponent myContainer; protected final OperationContext myContext; protected List myComponents; - public AbstractEditOperation(OperationContext context) { + public AbstractEditOperation(RadComponent container, OperationContext context) { + myContainer = container; myContext = context; } diff --git a/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/designSurface/CaptionPanel.java b/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/designSurface/CaptionPanel.java index b1038253d6c3..724960490297 100644 --- a/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/designSurface/CaptionPanel.java +++ b/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/designSurface/CaptionPanel.java @@ -26,6 +26,7 @@ import com.intellij.openapi.actionSystem.DataProvider; import com.intellij.openapi.actionSystem.PlatformDataKeys; import com.intellij.ui.IdeBorderFactory; import com.intellij.ui.SideBorder; +import com.intellij.util.containers.IntArrayList; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -34,6 +35,7 @@ import javax.swing.*; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import java.awt.*; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -41,6 +43,8 @@ import java.util.List; * @author Alexander Lobas */ public class CaptionPanel extends JLayeredPane implements DataProvider, DeleteProvider { + private static final int SIZE = 16; + private final boolean myHorizontal; private final EditableArea myMainArea; private final EditableArea myArea; @@ -49,6 +53,7 @@ public class CaptionPanel extends JLayeredPane implements DataProvider, DeletePr private final CommonEditActionsProvider myActionsProvider; private final RadVisualComponent myRootComponent; private List myRootChildren = Collections.emptyList(); + private ICaption myCaption; public CaptionPanel(DesignerEditorPanel designer, boolean horizontal) { setBorder(IdeBorderFactory.createBorder(horizontal ? SideBorder.BOTTOM : SideBorder.RIGHT)); @@ -69,7 +74,12 @@ public class CaptionPanel extends JLayeredPane implements DataProvider, DeletePr } }; myRootComponent.setNativeComponent(this); - myRootComponent.setBounds(0, 0, 100000, 100000); + if (horizontal) { + myRootComponent.setBounds(0, 0, 100000, SIZE); + } + else { + myRootComponent.setBounds(0, 0, SIZE, 100000); + } myArea = new ComponentEditableArea(this) { @Override @@ -157,7 +167,7 @@ public class CaptionPanel extends JLayeredPane implements DataProvider, DeletePr @Override public Dimension getPreferredSize() { - return new Dimension(16, 16); + return new Dimension(SIZE, SIZE); } @Override @@ -191,6 +201,15 @@ public class CaptionPanel extends JLayeredPane implements DataProvider, DeletePr boolean update = !myRootChildren.isEmpty(); + IntArrayList oldSelection = null; + if (myCaption != null) { + oldSelection = new IntArrayList(); + for (RadComponent component : myArea.getSelection()) { + oldSelection.add(myRootChildren.indexOf(component)); + } + } + + myArea.deselectAll(); myRootComponent.setLayout(null); ICaption caption = null; @@ -209,10 +228,34 @@ public class CaptionPanel extends JLayeredPane implements DataProvider, DeletePr } else { myRootComponent.setLayout(caption.getCaptionLayout(myMainArea, myHorizontal)); + myRootChildren = caption.getCaptionChildren(myMainArea, myHorizontal); + for (RadComponent child : myRootChildren) { + child.setParent(myRootComponent); + } + + if (myCaption == caption) { + List newSelection = new ArrayList(); + int componentSize = myRootChildren.size(); + int selectionSize = oldSelection.size(); + + for (int i = 0; i < selectionSize; i++) { + int index = oldSelection.get(i); + if (0 <= index && index < componentSize) { + newSelection.add(myRootChildren.get(index)); + } + } + + if (!newSelection.isEmpty()) { + myArea.setSelection(newSelection); + } + } + update |= !myRootChildren.isEmpty(); } + myCaption = caption; + if (update) { revalidate(); repaint(); diff --git a/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/designSurface/FlowBaseOperation.java b/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/designSurface/FlowBaseOperation.java new file mode 100644 index 000000000000..2ee609d8c2fa --- /dev/null +++ b/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/designSurface/FlowBaseOperation.java @@ -0,0 +1,188 @@ +/* + * Copyright 2000-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.designer.designSurface; + +import com.intellij.designer.designSurface.feedbacks.LineInsertFeedback; +import com.intellij.designer.designSurface.feedbacks.RectangleFeedback; +import com.intellij.designer.model.RadComponent; +import org.jetbrains.annotations.Nullable; + +import java.awt.*; +import java.util.List; + +/** + * @author Alexander Lobas + */ +public abstract class FlowBaseOperation extends AbstractEditOperation { + protected final boolean myHorizontal; + protected RectangleFeedback myFirstInsertFeedback; + protected LineInsertFeedback myInsertFeedback; + protected Rectangle myBounds; + protected RadComponent myChildTarget; + protected boolean myInsertBefore; + + public FlowBaseOperation(RadComponent container, OperationContext context, boolean horizontal) { + super(container, context); + myHorizontal = horizontal; + } + + protected void createFeedback() { + if (myFirstInsertFeedback == null) { + FeedbackLayer layer = myContext.getArea().getFeedbackLayer(); + myBounds = myContainer.getBounds(layer); + + myFirstInsertFeedback = new RectangleFeedback(Color.green, 2); + myFirstInsertFeedback.setBounds(myBounds); + + myInsertFeedback = new LineInsertFeedback(Color.green, !myHorizontal); + myInsertFeedback.size(myBounds.width, myBounds.height); + + if (myContainer.getChildren().isEmpty()) { + layer.add(myFirstInsertFeedback); + } + else { + layer.add(myInsertFeedback); + } + layer.repaint(); + } + } + + @Override + public void showFeedback() { + createFeedback(); + + if (!myContainer.getChildren().isEmpty()) { + FeedbackLayer layer = myContext.getArea().getFeedbackLayer(); + Point location = myContext.getLocation(); + myChildTarget = null; + + if (myHorizontal) { + for (RadComponent child : myContainer.getChildren()) { + Rectangle childBounds = getBounds(child, layer); + if (childBounds.x <= location.x && location.x <= childBounds.getMaxX()) { + myChildTarget = child; + break; + } + } + } + else { + for (RadComponent child : myContainer.getChildren()) { + Rectangle childBounds = getBounds(child, layer); + if (childBounds.y <= location.y && location.y <= childBounds.getMaxY()) { + myChildTarget = child; + break; + } + } + } + if (myChildTarget == null) { + myChildTarget = getSideChildTarget(); + } + + Rectangle targetBounds = getBounds(myChildTarget, layer); + if (myHorizontal) { + myInsertBefore = location.x < targetBounds.getCenterX(); + + if (myInsertBefore) { + myInsertFeedback.setLocation(targetBounds.x, myBounds.y); + } + else { + myInsertFeedback.setLocation(targetBounds.x + targetBounds.width, myBounds.y); + } + } + else { + myInsertBefore = location.y < targetBounds.getCenterY(); + + if (myInsertBefore) { + myInsertFeedback.setLocation(myBounds.x, targetBounds.y); + } + else { + myInsertFeedback.setLocation(myBounds.x, targetBounds.y + targetBounds.height); + } + } + + layer.repaint(); + } + } + + protected Rectangle getBounds(RadComponent component, FeedbackLayer layer) { + return component.getBounds(layer); + } + + private RadComponent getSideChildTarget() { + Point location = myContext.getLocation(); + List children = myContainer.getChildren(); + RadComponent lastChild = children.get(children.size() - 1); + Rectangle childBounds = lastChild.getBounds(myContext.getArea().getFeedbackLayer()); + + if (myHorizontal) { + if (location.x >= childBounds.getMaxX()) { + return lastChild; + } + return children.get(0); + } + if (location.y >= childBounds.getMaxY()) { + return lastChild; + } + return children.get(0); + } + + @Override + public void eraseFeedback() { + if (myFirstInsertFeedback != null) { + FeedbackLayer layer = myContext.getArea().getFeedbackLayer(); + layer.remove(myFirstInsertFeedback); + layer.remove(myInsertFeedback); + layer.repaint(); + myFirstInsertFeedback = null; + myInsertFeedback = null; + } + } + + @Override + public boolean canExecute() { + if (myContext.isMove()) { + if (myComponents.contains(myChildTarget)) { + return false; + } + + List children = myContainer.getChildren(); + int index = children.indexOf(myChildTarget) + (myInsertBefore ? -1 : 1); + if (0 <= index && index < children.size()) { + return !myComponents.contains(children.get(index)); + } + } + return true; + } + + @Override + public void execute() throws Exception { + if (myChildTarget == null || myInsertBefore) { + execute(myChildTarget); + } + else { + List children = myContainer.getChildren(); + int index = children.indexOf(myChildTarget) + 1; + if (index < children.size()) { + execute(children.get(index)); + } + else { + execute(null); + } + } + } + + protected abstract void execute(@Nullable RadComponent insertBefore) throws Exception; +} \ No newline at end of file diff --git a/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/designSurface/tools/MarqueeTracker.java b/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/designSurface/tools/MarqueeTracker.java index 0f22d85b4ce1..2d58e62b073f 100644 --- a/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/designSurface/tools/MarqueeTracker.java +++ b/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/designSurface/tools/MarqueeTracker.java @@ -98,12 +98,10 @@ public class MarqueeTracker extends InputTool { if (myState == STATE_DRAG_IN_PROGRESS) { return getDefaultCursor(); } - else if (myState == STATE_INVALID) { + if (myState == STATE_INVALID) { return getDisabledCursor(); } - else { - return null; - } + return null; } private void showFeedback() { diff --git a/resources-en/src/messages/CompilerBundle.properties b/resources-en/src/messages/CompilerBundle.properties index bcac3fc731f1..03756dd9ce92 100644 --- a/resources-en/src/messages/CompilerBundle.properties +++ b/resources-en/src/messages/CompilerBundle.properties @@ -165,6 +165,10 @@ element.type.name.module.test.output=Module Test Output element.type.name.directory=Directory element.type.name.archive=Archive artifact.type.plain=Other +build.artifacts.before.run.description.empty=Build Artifacts +build.artifacts.before.run.description.single=Build ''{0}'' artifact +build.artifacts.before.run.description.multiple=Build {0} artifacts +build.artifacts.before.run.selector.title=Select Artifacts message.text.destination.is.directory={0} Destination is a directory. message.text.error.copying.file.to.file=Error copying ''{0}'' to ''{1}''. diff --git a/resources/src/idea/JavaActions.xml b/resources/src/idea/JavaActions.xml index 675fcf4d2022..d163c65f0ddb 100644 --- a/resources/src/idea/JavaActions.xml +++ b/resources/src/idea/JavaActions.xml @@ -23,13 +23,6 @@ - - - - - - - diff --git a/resources/src/idea/RichPlatformPlugin.xml b/resources/src/idea/RichPlatformPlugin.xml index 950b924e7452..5355ffbc6da3 100644 --- a/resources/src/idea/RichPlatformPlugin.xml +++ b/resources/src/idea/RichPlatformPlugin.xml @@ -316,6 +316,7 @@ + diff --git a/xml/impl/src/com/intellij/codeInsight/daemon/impl/quickfix/FetchExtResourceAction.java b/xml/impl/src/com/intellij/codeInsight/daemon/impl/quickfix/FetchExtResourceAction.java index 78e3e6f4a4ff..ff395f1acd04 100644 --- a/xml/impl/src/com/intellij/codeInsight/daemon/impl/quickfix/FetchExtResourceAction.java +++ b/xml/impl/src/com/intellij/codeInsight/daemon/impl/quickfix/FetchExtResourceAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2012 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package com.intellij.codeInsight.daemon.impl.quickfix; import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; import com.intellij.javaee.ExternalResourceManager; +import com.intellij.openapi.application.AccessToken; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.PathManager; import com.intellij.openapi.diagnostic.Logger; @@ -31,6 +32,7 @@ import com.intellij.openapi.roots.WatchedRootsProvider; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; @@ -166,26 +168,23 @@ public class FetchExtResourceAction extends BaseExtResourceAction implements Wat }); } - private void fetchDtd(final Project project, final String dtdUrl, final String url, final ProgressIndicator indicator) - throws IOException { - + private void fetchDtd(final Project project, final String dtdUrl, final String url, final ProgressIndicator indicator) throws IOException { final String extResourcesPath = getExternalResourcesPath(); final File extResources = new File(extResourcesPath); - final boolean alreadyExists = extResources.exists(); - extResources.mkdirs(); - LOG.assertTrue(extResources.exists()); + LOG.assertTrue(extResources.mkdirs() || extResources.exists(), extResources); final PsiManager psiManager = PsiManager.getInstance(project); ApplicationManager.getApplication().invokeAndWait(new Runnable() { public void run() { - ApplicationManager.getApplication().runWriteAction(new Runnable() { - public void run() { - VirtualFile vFile = LocalFileSystem.getInstance().refreshAndFindFileByPath( - extResources.getAbsolutePath().replace(File.separatorChar, '/')); - LOG.assertTrue(vFile != null); - if (!alreadyExists) LocalFileSystem.getInstance().addRootToWatch(vFile.getPath(), true); - } - }); + final AccessToken token = ApplicationManager.getApplication().acquireWriteActionLock(FetchExtResourceAction.class); + try { + final String path = FileUtil.toSystemIndependentName(extResources.getAbsolutePath()); + final VirtualFile vFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(path); + LOG.assertTrue(vFile != null, path); + } + finally { + token.finish(); + } } }, indicator.getModalityState()); diff --git a/xml/impl/src/com/intellij/psi/impl/source/xml/XmlTagImpl.java b/xml/impl/src/com/intellij/psi/impl/source/xml/XmlTagImpl.java index 096e11b4961b..67c0a85a6aea 100644 --- a/xml/impl/src/com/intellij/psi/impl/source/xml/XmlTagImpl.java +++ b/xml/impl/src/com/intellij/psi/impl/source/xml/XmlTagImpl.java @@ -52,7 +52,10 @@ import com.intellij.util.IncorrectOperationException; import com.intellij.util.PlatformIcons; import com.intellij.util.containers.BidirectionalMap; import com.intellij.util.containers.ContainerUtil; -import com.intellij.xml.*; +import com.intellij.xml.XmlAttributeDescriptor; +import com.intellij.xml.XmlElementDescriptor; +import com.intellij.xml.XmlExtension; +import com.intellij.xml.XmlNSDescriptor; import com.intellij.xml.impl.dtd.XmlNSDescriptorImpl; import com.intellij.xml.impl.schema.AnyXmlElementDescriptor; import com.intellij.xml.util.XmlTagUtil; @@ -861,11 +864,7 @@ public class XmlTagImpl extends XmlElementImpl implements XmlTag { private String getNsLocation(String ns) { if (XmlUtil.XHTML_URI.equals(ns)) { - String defaultHtmlDoctype = ExternalResourceManagerEx.getInstanceEx().getDefaultHtmlDoctype(getProject()); - if (Html5SchemaProvider.HTML5_SCHEMA_LOCATION.equals(defaultHtmlDoctype)) { - defaultHtmlDoctype = Html5SchemaProvider.XHTML5_SCHEMA_LOCATION; - } - return defaultHtmlDoctype; + return XmlUtil.getDefaultXhtmlNamespace(getProject()); } return ns; } diff --git a/xml/impl/src/com/intellij/xml/util/XmlUtil.java b/xml/impl/src/com/intellij/xml/util/XmlUtil.java index da8f6f012152..8fff78934727 100644 --- a/xml/impl/src/com/intellij/xml/util/XmlUtil.java +++ b/xml/impl/src/com/intellij/xml/util/XmlUtil.java @@ -602,6 +602,14 @@ public class XmlUtil { compositeElement.addChildren(children[2].getNode(), null, null); } + + public static String getDefaultXhtmlNamespace(Project project) { + String defaultHtmlDoctype = ExternalResourceManagerEx.getInstanceEx().getDefaultHtmlDoctype(project); + if (Html5SchemaProvider.HTML5_SCHEMA_LOCATION.equals(defaultHtmlDoctype)) { + defaultHtmlDoctype = Html5SchemaProvider.XHTML5_SCHEMA_LOCATION; + } + return defaultHtmlDoctype; + } // //public static void expandTag(@NotNull XmlTag tag) { // final LeafElement emptyTagEnd = (LeafElement)XmlChildRole.EMPTY_TAG_END_FINDER.findChild(tag.getNode());