From 63abcdc97d5805fd5b5eb87506e961e813e77d25 Mon Sep 17 00:00:00 2001 From: "Maxim.Medvedev" Date: Fri, 21 Oct 2011 12:46:13 +0400 Subject: [PATCH 01/19] fix toString templates. Default template should overload a user's one with the same name --- .../tostring/template/DefaultBuffer.vm | 2 +- .../tostring/template/DefaultBuilder.vm | 2 +- .../tostring/template/TemplatesManager.java | 21 +++++++++++++------ 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/plugins/generate-tostring/src/org/jetbrains/generate/tostring/template/DefaultBuffer.vm b/plugins/generate-tostring/src/org/jetbrains/generate/tostring/template/DefaultBuffer.vm index 24906bdbffb1..7c4f30828d8b 100644 --- a/plugins/generate-tostring/src/org/jetbrains/generate/tostring/template/DefaultBuffer.vm +++ b/plugins/generate-tostring/src/org/jetbrains/generate/tostring/template/DefaultBuffer.vm @@ -17,7 +17,7 @@ $member.name=")## #if ($member.primitiveArray) .append($member.name == null ? "null" : ""); for (int i = 0; $member.name != null && i < $member.name .length; ++i) - sb.append(i == 0 ? "" : ", ").append($member.name[i]); + sb.append(i == 0 ? "" : ", ").append($member.name[$i]); #elseif ($member.objectArray) .append($member.name == null ? "null" : Arrays.asList($member.name).toString()); #elseif ($member.string) diff --git a/plugins/generate-tostring/src/org/jetbrains/generate/tostring/template/DefaultBuilder.vm b/plugins/generate-tostring/src/org/jetbrains/generate/tostring/template/DefaultBuilder.vm index 2654d0ce945a..e424555dbf64 100644 --- a/plugins/generate-tostring/src/org/jetbrains/generate/tostring/template/DefaultBuilder.vm +++ b/plugins/generate-tostring/src/org/jetbrains/generate/tostring/template/DefaultBuilder.vm @@ -17,7 +17,7 @@ $member.name=")## #if ($member.primitiveArray) .append($member.name == null ? "null" : ""); for (int i = 0; $member.name != null && i < $member.name .length; ++i) - sb.append(i == 0 ? "" : ", ").append($member.name[i]); + sb.append(i == 0 ? "" : ", ").append($member.name[$i]); #elseif ($member.objectArray) .append($member.name == null ? "null" : Arrays.asList($member.name).toString()); #elseif ($member.string) diff --git a/plugins/generate-tostring/src/org/jetbrains/generate/tostring/template/TemplatesManager.java b/plugins/generate-tostring/src/org/jetbrains/generate/tostring/template/TemplatesManager.java index 1cc2e591612d..5ac07766aeb3 100644 --- a/plugins/generate-tostring/src/org/jetbrains/generate/tostring/template/TemplatesManager.java +++ b/plugins/generate-tostring/src/org/jetbrains/generate/tostring/template/TemplatesManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2011 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -69,13 +69,22 @@ public class TemplatesManager implements PersistentStateComponent getAllTemplates() { - Collection templates = new LinkedHashSet(Arrays.asList(TemplateResourceLocator.getDefaultTemplates())); - templates.addAll(myState.templates); - return templates; + public Collection getAllTemplates() { + TemplateResource[] defaultTemplates = TemplateResourceLocator.getDefaultTemplates(); + HashSet names = new HashSet(); + for (TemplateResource defaultTemplate : defaultTemplates) { + names.add(defaultTemplate.getFileName()); } + Collection templates = new LinkedHashSet(Arrays.asList(defaultTemplates)); + for (TemplateResource template : myState.templates) { + if (!names.contains(template.getFileName())) { + templates.add(template); + } + } + return templates; + } - public TemplateResource getDefaultTemplate() { + public TemplateResource getDefaultTemplate() { for (TemplateResource template : getAllTemplates()) { if (Comparing.equal(template.getFileName(), myState.defaultTempalteName)) { return template; From aa9b3b1b65e822640ea25af14f923b39d0d782f6 Mon Sep 17 00:00:00 2001 From: Dmitry Avdeev Date: Thu, 20 Oct 2011 16:04:21 +0400 Subject: [PATCH 02/19] diagnostics --- .../core-api/src/com/intellij/psi/PsiElementResolveResult.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/platform/core-api/src/com/intellij/psi/PsiElementResolveResult.java b/platform/core-api/src/com/intellij/psi/PsiElementResolveResult.java index 2479e4c6852d..dc07cd2cb3ed 100644 --- a/platform/core-api/src/com/intellij/psi/PsiElementResolveResult.java +++ b/platform/core-api/src/com/intellij/psi/PsiElementResolveResult.java @@ -68,7 +68,8 @@ public class PsiElementResolveResult implements ResolveResult{ @NonNls public String toString() { - return "PsiElementResolveResult: " + (myElement instanceof PsiNamedElement ? ((PsiNamedElement)myElement).getName() : myElement.getText()); + return "PsiElementResolveResult with " + myElement.getClass() + ": " + + (myElement instanceof PsiNamedElement ? ((PsiNamedElement)myElement).getName() : myElement.getText()); } public static ResolveResult[] createResults(@Nullable Collection elements) { From cf9bf0e52ccfe85d6fffb39c00d0deb9a9065370 Mon Sep 17 00:00:00 2001 From: Sergey Evdokimov Date: Thu, 20 Oct 2011 19:54:36 +0400 Subject: [PATCH 03/19] Improve class loading in GroovyMethodInfo. --- .../extensions/GroovyMethodDescriptor.java | 6 ++-- .../groovy/extensions/GroovyMethodInfo.java | 8 +++-- .../groovy/util/ClassInstanceCache.java | 35 ++++--------------- 3 files changed, 14 insertions(+), 35 deletions(-) diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/extensions/GroovyMethodDescriptor.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/extensions/GroovyMethodDescriptor.java index d2b2b2338f1a..eae55b709bd7 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/extensions/GroovyMethodDescriptor.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/extensions/GroovyMethodDescriptor.java @@ -1,21 +1,19 @@ package org.jetbrains.plugins.groovy.extensions; +import com.intellij.openapi.extensions.AbstractExtensionPointBean; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.psi.PsiMethod; import com.intellij.util.xmlb.annotations.AbstractCollection; import com.intellij.util.xmlb.annotations.Attribute; import com.intellij.util.xmlb.annotations.Property; import com.intellij.util.xmlb.annotations.Tag; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrCall; import java.util.*; /** * @author Sergey Evdokimov */ -public class GroovyMethodDescriptor { +public class GroovyMethodDescriptor extends AbstractExtensionPointBean { private static final String ATTR_NAMES_DELIMITER = " \t\n\r,;"; diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/extensions/GroovyMethodInfo.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/extensions/GroovyMethodInfo.java index 2e6f829b7f10..c691f51215ef 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/extensions/GroovyMethodInfo.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/extensions/GroovyMethodInfo.java @@ -21,7 +21,8 @@ public class GroovyMethodInfo { private static Map>> LIGHT_METHOD_INFOS; private final List myParams; - + private final ClassLoader myClassLoader; + private final String myReturnType; private final String myReturnTypeCalculatorClassName; private PairFunction myReturnTypeCalculatorInstance; @@ -121,6 +122,7 @@ public class GroovyMethodInfo { } public GroovyMethodInfo(GroovyMethodDescriptor method) { + myClassLoader = method.getLoaderForClass(); myParams = method.getParams(); myReturnType = method.returnType; myReturnTypeCalculatorClassName = method.returnTypeCalculator; @@ -177,7 +179,7 @@ public class GroovyMethodInfo { @NotNull public PairFunction getReturnTypeCalculator() { if (myReturnTypeCalculatorInstance == null) { - myReturnTypeCalculatorInstance = ClassInstanceCache.getInstance(myReturnTypeCalculatorClassName); + myReturnTypeCalculatorInstance = ClassInstanceCache.getInstance(myReturnTypeCalculatorClassName, myClassLoader); } return myReturnTypeCalculatorInstance; } @@ -193,7 +195,7 @@ public class GroovyMethodInfo { public GroovyNamedArgumentProvider getNamedArgProvider() { if (myNamedArgProviderInstance == null) { - myNamedArgProviderInstance = ClassInstanceCache.getInstance(myNamedArgProviderClassName); + myNamedArgProviderInstance = ClassInstanceCache.getInstance(myNamedArgProviderClassName, myClassLoader); } return myNamedArgProviderInstance; } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/util/ClassInstanceCache.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/util/ClassInstanceCache.java index bf536189f4b1..7a9f0554e661 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/util/ClassInstanceCache.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/util/ClassInstanceCache.java @@ -1,7 +1,5 @@ package org.jetbrains.plugins.groovy.util; -import com.intellij.ide.plugins.IdeaPluginDescriptor; -import com.intellij.ide.plugins.PluginManager; import com.intellij.util.containers.ConcurrentHashMap; import org.jetbrains.annotations.NotNull; @@ -15,34 +13,16 @@ public class ClassInstanceCache { private ClassInstanceCache() { } - private static Object createInstance(@NotNull String className) { - try { - try { - return Class.forName(className).newInstance(); - } - catch (ClassNotFoundException e) { - for (IdeaPluginDescriptor descriptor : PluginManager.getPlugins()) { - try { - return descriptor.getPluginClassLoader().loadClass(className).newInstance(); - } - catch (ClassNotFoundException ignored) { - - } - } - - throw new RuntimeException("Class not found: " + className); - } - } - catch (Exception e) { - throw new RuntimeException(e); - } - } - @SuppressWarnings("unchecked") - public static T getInstance(@NotNull String className) { + public static T getInstance(@NotNull String className, ClassLoader classLoader) { Object res = CACHE.get(className); if (res == null) { - res = createInstance(className); + try { + res = classLoader.loadClass(className).newInstance(); + } + catch (Exception e) { + throw new RuntimeException(e); + } Object oldValue = CACHE.putIfAbsent(className, res); if (oldValue != null) { @@ -52,5 +32,4 @@ public class ClassInstanceCache { return (T)res; } - } From aa57b0c7e54208d3fbd3aa91428c82acafb05bb4 Mon Sep 17 00:00:00 2001 From: Sergey Evdokimov Date: Fri, 21 Oct 2011 14:41:06 +0400 Subject: [PATCH 04/19] Bug fix: return type for 'DomainClass.withSession()' calculates incorrectly for grails 1.3.7 --- .../GrDescriptorReturnTypeCalculator.java | 41 +----------- .../groovy/util/GroovyStdTypeCalculators.java | 67 +++++++++++++++++++ 2 files changed, 68 insertions(+), 40 deletions(-) create mode 100644 plugins/groovy/src/org/jetbrains/plugins/groovy/util/GroovyStdTypeCalculators.java diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/path/GrDescriptorReturnTypeCalculator.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/path/GrDescriptorReturnTypeCalculator.java index 92862d15a748..fe58efe81b36 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/path/GrDescriptorReturnTypeCalculator.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/path/GrDescriptorReturnTypeCalculator.java @@ -1,33 +1,24 @@ package org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.path; -import com.intellij.openapi.util.NullableComputable; -import com.intellij.openapi.util.RecursionGuard; -import com.intellij.openapi.util.RecursionManager; import com.intellij.psi.JavaPsiFacade; import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiType; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.extensions.GroovyMethodInfo; -import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock; -import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall; -import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil; /** * @author Sergey Evdokimov */ public class GrDescriptorReturnTypeCalculator extends GrCallExpressionTypeCalculator { - private static final RecursionGuard ourGuard = RecursionManager.createGuard("GrDescriptorReturnTypeCalculator getClosureReturnType"); - @Override public PsiType calculateReturnType(@NotNull GrMethodCall callExpression, @NotNull PsiMethod method) { for (GroovyMethodInfo methodInfo : GroovyMethodInfo.getInfos(method)) { String returnType = methodInfo.getReturnType(); if (returnType != null) { if (methodInfo.isApplicable(method)) { - return typeFromText(returnType, callExpression); + return JavaPsiFacade.getElementFactory(callExpression.getProject()).createTypeFromText(returnType, callExpression); } } else { @@ -44,34 +35,4 @@ public class GrDescriptorReturnTypeCalculator extends GrCallExpressionTypeCalcul return null; } - - @Nullable - public static PsiType typeFromText(String typeName, GrMethodCall callExpression) { - if (typeName.equals("!closure")) { - GrExpression[] allArguments = PsiUtil.getAllArguments(callExpression); - GrClosableBlock closure = null; - - for (GrExpression argument : allArguments) { - if (argument instanceof GrClosableBlock) { - closure = (GrClosableBlock)argument; - break; - } - } - - if (closure == null) return null; - - final GrClosableBlock finalClosure = closure; - - return ourGuard.doPreventingRecursion(callExpression, true, new NullableComputable() { - @Override - public PsiType compute() { - PsiType returnType = finalClosure.getReturnType(); - if (returnType == PsiType.VOID) return null; - return returnType; - } - }); - } - - return JavaPsiFacade.getElementFactory(callExpression.getProject()).createTypeFromText(typeName, callExpression); - } } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/util/GroovyStdTypeCalculators.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/util/GroovyStdTypeCalculators.java new file mode 100644 index 000000000000..b26fac13fc0f --- /dev/null +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/util/GroovyStdTypeCalculators.java @@ -0,0 +1,67 @@ +/* + * Copyright 2000-2011 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.plugins.groovy.util; + +import com.intellij.openapi.util.NullableComputable; +import com.intellij.openapi.util.RecursionGuard; +import com.intellij.openapi.util.RecursionManager; +import com.intellij.psi.PsiMethod; +import com.intellij.psi.PsiType; +import com.intellij.util.PairFunction; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall; +import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil; + +/** + * @author Sergey Evdokimov + */ +public class GroovyStdTypeCalculators { + + private static final RecursionGuard ourGuard = RecursionManager.createGuard("GrDescriptorReturnTypeCalculator getClosureReturnType"); + + private GroovyStdTypeCalculators() { + } + + public static class ClosureTypeExtractor implements PairFunction { + @Override + public PsiType fun(GrMethodCall methodCall, PsiMethod method) { + GrExpression[] allArguments = PsiUtil.getAllArguments(methodCall); + GrClosableBlock closure = null; + + for (GrExpression argument : allArguments) { + if (argument instanceof GrClosableBlock) { + closure = (GrClosableBlock)argument; + break; + } + } + + if (closure == null) return null; + + final GrClosableBlock finalClosure = closure; + + return ourGuard.doPreventingRecursion(methodCall, true, new NullableComputable() { + @Override + public PsiType compute() { + PsiType returnType = finalClosure.getReturnType(); + if (returnType == PsiType.VOID) return null; + return returnType; + } + }); + } + } + +} From 33ff3c82647fb7758903211da2ea6cc26a724043 Mon Sep 17 00:00:00 2001 From: Konstantin Bulenkov Date: Fri, 21 Oct 2011 13:09:19 +0200 Subject: [PATCH 05/19] goto related files for tests --- .../IdeaTestAssistant/src/META-INF/plugin.xml | 1 + .../GotoTestRelatedProvider.java | 63 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 plugins/IdeaTestAssistant/src/com/intellij/testAssistant/GotoTestRelatedProvider.java diff --git a/plugins/IdeaTestAssistant/src/META-INF/plugin.xml b/plugins/IdeaTestAssistant/src/META-INF/plugin.xml index df0a2d1ce721..460595db0e63 100644 --- a/plugins/IdeaTestAssistant/src/META-INF/plugin.xml +++ b/plugins/IdeaTestAssistant/src/META-INF/plugin.xml @@ -18,6 +18,7 @@ + diff --git a/plugins/IdeaTestAssistant/src/com/intellij/testAssistant/GotoTestRelatedProvider.java b/plugins/IdeaTestAssistant/src/com/intellij/testAssistant/GotoTestRelatedProvider.java new file mode 100644 index 000000000000..1a9d044a558e --- /dev/null +++ b/plugins/IdeaTestAssistant/src/com/intellij/testAssistant/GotoTestRelatedProvider.java @@ -0,0 +1,63 @@ +/* + * Copyright 2000-2011 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.testAssistant; + +import com.intellij.navigation.GotoRelatedItem; +import com.intellij.navigation.GotoRelatedProvider; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiFile; +import com.intellij.psi.search.FilenameIndex; +import com.intellij.psi.search.GlobalSearchScope; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Konstantin Bulenkov + */ +public class GotoTestRelatedProvider extends GotoRelatedProvider { + @NotNull + @Override + public List getItems(@NotNull DataContext context) { + final VirtualFile file = PlatformDataKeys.VIRTUAL_FILE.getData(context); + final Project project = PlatformDataKeys.PROJECT.getData(context); + if (file != null && project != null) { + final String ext = file.getExtension(); + String name = file.getNameWithoutExtension(); + final String filename; + if (ext != null && ext.length() > 0 && name.length() > 0) { + if (name.endsWith("Test") && name.length() > 4) { + filename = name.substring(0, name.length() - 4) + "." + ext; + } else { + filename = name + "Test." + ext; + } + final PsiFile[] files = FilenameIndex.getFilesByName(project, filename, GlobalSearchScope.allScope(project)); + if (files.length > 0) { + List items = new ArrayList(); + for (PsiFile psiFile : files) { + items.add(new GotoRelatedItem(psiFile)); + } + return items; + } + } + } + return super.getItems(context); + } +} From 7dc0091138ae5b232b8f75b7ebf7fb4bb72827f1 Mon Sep 17 00:00:00 2001 From: anna Date: Fri, 21 Oct 2011 13:12:48 +0200 Subject: [PATCH 06/19] save console output to file (IDEA-64394) --- .../debugger/ui/DebuggerSessionTab.java | 2 +- .../configurations/RunConfigurationBase.java | 43 ++++++ .../logging/LogConfigurationPanel.form | 26 +++- .../logging/LogConfigurationPanel.java | 26 +++- .../diagnostic/logging/LogFilesManager.java | 3 + .../diagnostic/logging/OutputFileUtil.java | 125 ++++++++++++++++++ .../execution/runners/RunContentBuilder.java | 6 +- .../sm/runner/ui/SMTestRunnerResultsForm.java | 1 + .../testframework/CompositePrintable.java | 40 ++++++ .../impl/ui/DebuggerSessionTabBase.java | 3 +- .../xdebugger/impl/ui/XDebugSessionTab.java | 2 +- .../execution/junit/JUnitConfiguration.java | 5 + .../intellij/execution/junit/TestObject.java | 1 + .../configuration/TestNGConfiguration.java | 5 + .../configuration/TestNGRunnableState.java | 1 + 15 files changed, 278 insertions(+), 11 deletions(-) create mode 100644 platform/lang-impl/src/com/intellij/diagnostic/logging/OutputFileUtil.java diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/DebuggerSessionTab.java b/java/debugger/impl/src/com/intellij/debugger/ui/DebuggerSessionTab.java index 2f6ceacc7b10..4b9a146a7ee4 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/DebuggerSessionTab.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/DebuggerSessionTab.java @@ -292,7 +292,7 @@ public class DebuggerSessionTab extends DebuggerSessionTabBase implements Dispos } console.setActions(consoleActions, ActionPlaces.DEBUGGER_TOOLBAR, myConsole.getPreferredFocusableComponent()); - initLogConsoles(myConfiguration, myRunContentDescriptor.getProcessHandler()); + initLogConsoles(myConfiguration, myRunContentDescriptor.getProcessHandler(), myConsole); DefaultActionGroup group = new DefaultActionGroup(); final Executor executor = DefaultDebugExecutor.getDebugExecutorInstance(); diff --git a/platform/lang-api/src/com/intellij/execution/configurations/RunConfigurationBase.java b/platform/lang-api/src/com/intellij/execution/configurations/RunConfigurationBase.java index 1cf1c0873c8b..b7c8d435dbb5 100644 --- a/platform/lang-api/src/com/intellij/execution/configurations/RunConfigurationBase.java +++ b/platform/lang-api/src/com/intellij/execution/configurations/RunConfigurationBase.java @@ -41,9 +41,16 @@ public abstract class RunConfigurationBase extends UserDataHolderBase implements private ArrayList myLogFiles = new ArrayList(); private ArrayList myPredefinedLogFiles = new ArrayList(); + @NonNls private static final String LOG_FILE = "log_file"; @NonNls private static final String PREDEFINED_LOG_FILE_ELEMENT = "predefined_log_file"; + @NonNls private static final String FILE_OUTPUT = "output_file"; + @NonNls private static final String SAVE = "is_save"; + @NonNls private static final String OUTPUT_FILE = "path"; + private final Icon myIcon; + private boolean mySaveOutput = false; + private String myFileOutputPath = null; protected RunConfigurationBase(final Project project, final ConfigurationFactory factory, final String name) { myProject = project; @@ -97,6 +104,8 @@ public abstract class RunConfigurationBase extends UserDataHolderBase implements final RunConfigurationBase runConfiguration = (RunConfigurationBase)super.clone(); runConfiguration.myLogFiles = new ArrayList(myLogFiles); runConfiguration.myPredefinedLogFiles = new ArrayList(myPredefinedLogFiles); + runConfiguration.myFileOutputPath = myFileOutputPath; + runConfiguration.mySaveOutput = mySaveOutput; copyCopyableDataTo(runConfiguration); return runConfiguration; } @@ -167,6 +176,12 @@ public abstract class RunConfigurationBase extends UserDataHolderBase implements logFile.readExternal((Element)fileElement); myPredefinedLogFiles.add(logFile); } + final Element fileOutputElement = element.getChild(FILE_OUTPUT); + if (fileOutputElement != null) { + myFileOutputPath = fileOutputElement.getAttributeValue(OUTPUT_FILE); + final String isSave = fileOutputElement.getAttributeValue(SAVE); + mySaveOutput = isSave != null && Boolean.parseBoolean(isSave); + } } public void writeExternal(Element element) throws WriteExternalException { @@ -180,6 +195,34 @@ public abstract class RunConfigurationBase extends UserDataHolderBase implements predefinedLogFile.writeExternal(fileElement); element.addContent(fileElement); } + final Element fileOutputPathElement = new Element(FILE_OUTPUT); + if (myFileOutputPath != null) { + fileOutputPathElement.setAttribute(OUTPUT_FILE, myFileOutputPath); + } + fileOutputPathElement.setAttribute(SAVE, String.valueOf(mySaveOutput)); + if (myFileOutputPath != null || mySaveOutput) { + element.addContent(fileOutputPathElement); + } + } + + public boolean isSaveOutputToFile() { + return mySaveOutput; + } + + public void setSaveOutputToFile(boolean redirectOutput) { + mySaveOutput = redirectOutput; + } + + public String getOutputFilePath() { + return myFileOutputPath; + } + + public void setFileOutputPath(String fileOutputPath) { + myFileOutputPath = fileOutputPath; + } + + public boolean collectOutputFromProcessHandler() { + return true; } public boolean excludeCompileBeforeLaunchOption() { diff --git a/platform/lang-impl/src/com/intellij/diagnostic/logging/LogConfigurationPanel.form b/platform/lang-impl/src/com/intellij/diagnostic/logging/LogConfigurationPanel.form index 7c6c49816b2f..46bbd8eb0552 100644 --- a/platform/lang-impl/src/com/intellij/diagnostic/logging/LogConfigurationPanel.form +++ b/platform/lang-impl/src/com/intellij/diagnostic/logging/LogConfigurationPanel.form @@ -1,6 +1,6 @@
- + @@ -14,7 +14,7 @@ - + @@ -52,12 +52,32 @@ - + + + + + + + + + + + + + + + + + + + + + diff --git a/platform/lang-impl/src/com/intellij/diagnostic/logging/LogConfigurationPanel.java b/platform/lang-impl/src/com/intellij/diagnostic/logging/LogConfigurationPanel.java index 3f2509d7fee7..e694c228b26e 100644 --- a/platform/lang-impl/src/com/intellij/diagnostic/logging/LogConfigurationPanel.java +++ b/platform/lang-impl/src/com/intellij/diagnostic/logging/LogConfigurationPanel.java @@ -20,13 +20,18 @@ import com.intellij.diagnostic.DiagnosticBundle; import com.intellij.execution.configurations.LogFileOptions; import com.intellij.execution.configurations.PredefinedLogFile; import com.intellij.execution.configurations.RunConfigurationBase; +import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.options.SettingsEditor; +import com.intellij.openapi.ui.TextComponentAccessor; import com.intellij.openapi.ui.TextFieldWithBrowseButton; import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.ui.BooleanTableCellRenderer; import com.intellij.ui.ScrollPaneFactory; import com.intellij.ui.TableUtil; +import com.intellij.ui.components.JBCheckBox; import com.intellij.ui.table.TableView; import com.intellij.util.ui.AbstractTableCellEditor; import com.intellij.util.ui.CellEditorComponentWithBrowseButton; @@ -58,6 +63,8 @@ public class LogConfigurationPanel extends Setti private JButton myRemoveButton; private JPanel myScrollPanel; private JButton myEditButton; + private JBCheckBox myRedirectOutputCb; + private TextFieldWithBrowseButton myOutputFile; private final Map myLog2Predefined = new HashMap(); private final List myUnresolvedPredefined = new ArrayList(); @@ -144,6 +151,15 @@ public class LogConfigurationPanel extends Setti scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); myScrollPanel.add(scrollPane, BorderLayout.CENTER); myWholePanel.setPreferredSize(new Dimension(-1, 150)); + myOutputFile.addBrowseFolderListener("Choose File to Save Console Output", "Console output would be saved to the specified file", null, + FileChooserDescriptorFactory.createSingleFileOrFolderDescriptor(), + TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT); + myRedirectOutputCb.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + myOutputFile.setEnabled(myRedirectOutputCb.isSelected()); + } + }); } private void setUpColumnWidth(final JTableHeader tableHeader, final int preferredWidth, int columnIdx) { @@ -220,6 +236,11 @@ public class LogConfigurationPanel extends Setti } } myModel.setItems(list); + final boolean redirectOutputToFile = configuration.isSaveOutputToFile(); + myRedirectOutputCb.setSelected(redirectOutputToFile); + final String fileOutputPath = configuration.getOutputFilePath(); + myOutputFile.setText(fileOutputPath != null ? FileUtil.toSystemDependentName(fileOutputPath) : ""); + myOutputFile.setEnabled(redirectOutputToFile); } protected void applyEditorTo(final RunConfigurationBase configuration) throws ConfigurationException { @@ -244,7 +265,10 @@ public class LogConfigurationPanel extends Setti } for (PredefinedLogFile logFile : myUnresolvedPredefined) { configuration.addPredefinedLogFile(logFile); - } + } + final String text = myOutputFile.getText(); + configuration.setFileOutputPath(StringUtil.isEmpty(text) ? null : FileUtil.toSystemIndependentName(text)); + configuration.setSaveOutputToFile(myRedirectOutputCb.isSelected()); } @NotNull diff --git a/platform/lang-impl/src/com/intellij/diagnostic/logging/LogFilesManager.java b/platform/lang-impl/src/com/intellij/diagnostic/logging/LogFilesManager.java index 63f2c53097c5..e68799b6ba44 100644 --- a/platform/lang-impl/src/com/intellij/diagnostic/logging/LogFilesManager.java +++ b/platform/lang-impl/src/com/intellij/diagnostic/logging/LogFilesManager.java @@ -20,6 +20,7 @@ import com.intellij.execution.configurations.LogFileOptions; import com.intellij.execution.configurations.RunConfigurationBase; import com.intellij.execution.process.ProcessHandler; import com.intellij.openapi.Disposable; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Disposer; @@ -35,6 +36,8 @@ import java.util.*; * Date: 01-Feb-2006 */ public class LogFilesManager implements Disposable { + public static final Logger LOG = Logger.getInstance("#" + LogFilesManager.class.getName()); + private static final int UPDATE_INTERVAL = 500; private final Map> myLogFileManagerMap = new LinkedHashMap>(); diff --git a/platform/lang-impl/src/com/intellij/diagnostic/logging/OutputFileUtil.java b/platform/lang-impl/src/com/intellij/diagnostic/logging/OutputFileUtil.java new file mode 100644 index 000000000000..765a6b882f18 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/diagnostic/logging/OutputFileUtil.java @@ -0,0 +1,125 @@ +/* + * Copyright 2000-2011 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.diagnostic.logging; + +import com.intellij.execution.configurations.RunConfigurationBase; +import com.intellij.execution.filters.Filter; +import com.intellij.execution.filters.HyperlinkInfo; +import com.intellij.execution.process.ProcessAdapter; +import com.intellij.execution.process.ProcessEvent; +import com.intellij.execution.process.ProcessHandler; +import com.intellij.execution.process.ProcessOutputTypes; +import com.intellij.execution.ui.ConsoleView; +import com.intellij.execution.ui.ExecutionConsole; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.fileEditor.OpenFileDescriptor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.PrintStream; + +/** + * User: anna + * Date: 10/20/11 + */ +public class OutputFileUtil { + private static final String CONSOLE_OUTPUT_FILE_MESSAGE = "Console output is saving to: "; + + private OutputFileUtil() { + } + + public static void attachDumpListener(final RunConfigurationBase base, final ProcessHandler startedProcess, ExecutionConsole console) { + if (base.isSaveOutputToFile()) { + final String outputFilePath = base.getOutputFilePath(); + if (outputFilePath != null) { + final String filePath = FileUtil.toSystemDependentName(outputFilePath); + startedProcess.addProcessListener(new ProcessAdapter() { + private PrintStream myOutput; + @Override + public void onTextAvailable(ProcessEvent event, Key outputType) { + if (base.collectOutputFromProcessHandler() && myOutput != null && outputType != ProcessOutputTypes.SYSTEM) { + myOutput.print(event.getText()); + } + } + + @Override + public void startNotified(ProcessEvent event) { + try { + myOutput = new PrintStream(new FileOutputStream(new File(filePath))); + } + catch (FileNotFoundException ignored) { + } + startedProcess.notifyTextAvailable(CONSOLE_OUTPUT_FILE_MESSAGE + filePath + "\n", ProcessOutputTypes.SYSTEM); + } + + @Override + public void processTerminated(ProcessEvent event) { + startedProcess.removeProcessListener(this); + if (myOutput != null) { + myOutput.close(); + } + } + }); + if (console instanceof ConsoleView) { + ((ConsoleView)console).addMessageFilter(new ShowOutputFileFilter()); + } + } + } + } + + private static class ShowOutputFileFilter implements Filter { + @Override + public Result applyFilter(String line, int entireLength) { + if (line.startsWith(CONSOLE_OUTPUT_FILE_MESSAGE)) { + final String filePath = StringUtil.trimEnd(line.substring(CONSOLE_OUTPUT_FILE_MESSAGE.length()), "\n"); + + return new Result(entireLength - filePath.length() - 1, entireLength, new HyperlinkInfo() { + @Override + public void navigate(final Project project) { + final VirtualFile file = + ApplicationManager.getApplication().runWriteAction(new Computable() { + @Nullable + @Override + public VirtualFile compute() { + return LocalFileSystem.getInstance().refreshAndFindFileByPath(FileUtil.toSystemIndependentName(filePath)); + } + }); + + if (file != null) { + file.refresh(false, false); + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + FileEditorManager.getInstance(project).openTextEditor(new OpenFileDescriptor(project, file), true); + } + }); + } + } + }); + } + return null; + } + } +} diff --git a/platform/lang-impl/src/com/intellij/execution/runners/RunContentBuilder.java b/platform/lang-impl/src/com/intellij/execution/runners/RunContentBuilder.java index a637cc0668a5..5dc8d10083b1 100644 --- a/platform/lang-impl/src/com/intellij/execution/runners/RunContentBuilder.java +++ b/platform/lang-impl/src/com/intellij/execution/runners/RunContentBuilder.java @@ -15,10 +15,7 @@ */ package com.intellij.execution.runners; -import com.intellij.diagnostic.logging.AdditionalTabComponent; -import com.intellij.diagnostic.logging.LogConsoleImpl; -import com.intellij.diagnostic.logging.LogConsoleManager; -import com.intellij.diagnostic.logging.LogFilesManager; +import com.intellij.diagnostic.logging.*; import com.intellij.execution.DefaultExecutionResult; import com.intellij.execution.ExecutionResult; import com.intellij.execution.Executor; @@ -140,6 +137,7 @@ public class RunContentBuilder implements LogConsoleManager, Disposable { } if (profile instanceof RunConfigurationBase) { myManager.initLogConsoles((RunConfigurationBase)profile, myExecutionResult.getProcessHandler()); + OutputFileUtil.attachDumpListener((RunConfigurationBase)profile, myExecutionResult.getProcessHandler(), console); } } MyRunContentDescriptor contentDescriptor = new MyRunContentDescriptor(profile, myExecutionResult, myReuseProhibited, myUi.getComponent(), this); diff --git a/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/ui/SMTestRunnerResultsForm.java b/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/ui/SMTestRunnerResultsForm.java index 26268edc3731..34f025b606ac 100644 --- a/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/ui/SMTestRunnerResultsForm.java +++ b/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/ui/SMTestRunnerResultsForm.java @@ -112,6 +112,7 @@ public class SMTestRunnerResultsForm extends TestResultsPanel implements TestFra //Create tests common suite root //noinspection HardCodedStringLiteral myTestsRootNode = new SMTestProxy.SMRootTestProxy(); + //todo myTestsRootNode.setOutputFilePath(runConfiguration.getOutputFilePath()); // Fire selection changed and move focus on SHIFT+ENTER //TODO[romeo] improve diff --git a/platform/testRunner/src/com/intellij/execution/testframework/CompositePrintable.java b/platform/testRunner/src/com/intellij/execution/testframework/CompositePrintable.java index 5e8960547fd4..efe0d7bfd6aa 100644 --- a/platform/testRunner/src/com/intellij/execution/testframework/CompositePrintable.java +++ b/platform/testRunner/src/com/intellij/execution/testframework/CompositePrintable.java @@ -40,6 +40,7 @@ public class CompositePrintable implements Printable, Disposable { private final PrintablesWrapper myWrapper = new PrintablesWrapper(); protected int myExceptionMark; private int myCurrentSize = 0; + private String myOutputFile = null; private static final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD); public void flush() { @@ -119,6 +120,10 @@ public class CompositePrintable implements Printable, Disposable { myExceptionMark = exceptionMark; } + public void setOutputFilePath(String outputFile) { + myOutputFile = outputFile; + } + private static final Logger LOG = Logger.getInstance("#" + PrintablesWrapper.class.getName()); private class PrintablesWrapper { @@ -167,6 +172,41 @@ public class CompositePrintable implements Printable, Disposable { printable.printOn(myPrinter); } myPrinter.close(); + if (myOutputFile != null) { + PrintStream printStream = null; + try { + printStream = new PrintStream(new FileOutputStream(new File(myOutputFile), true)); + final PrintStream finalPrintStream = printStream; + for (Printable currentPrintable : currentPrintables) { + currentPrintable.printOn(new Printer() { + @Override + public void print(String text, ConsoleViewContentType contentType) { + if (contentType != ConsoleViewContentType.SYSTEM_OUTPUT) { + finalPrintStream.print(text); + } + } + + @Override + public void printHyperlink(String text, HyperlinkInfo info) { + finalPrintStream.print(text); + } + + @Override + public void onNewAvailable(@NotNull Printable printable) {} + @Override + public void mark() {} + }); + } + } + catch (FileNotFoundException e) { + LOG.error(e); + } + finally { + if (printStream != null) { + printStream.close(); + } + } + } } }; invokeInAlarm(request, ApplicationManager.getApplication().isUnitTestMode()); diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/DebuggerSessionTabBase.java b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/DebuggerSessionTabBase.java index 1b5cd0294d10..8d013ef5e41f 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/DebuggerSessionTabBase.java +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/DebuggerSessionTabBase.java @@ -80,9 +80,10 @@ public abstract class DebuggerSessionTabBase implements DebuggerLogConsoleManage } } - protected void initLogConsoles(final RunProfile runConfiguration, final ProcessHandler processHandler) { + protected void initLogConsoles(final RunProfile runConfiguration, final ProcessHandler processHandler, ExecutionConsole console) { if (runConfiguration instanceof RunConfigurationBase) { myManager.initLogConsoles((RunConfigurationBase)runConfiguration, processHandler); + OutputFileUtil.attachDumpListener((RunConfigurationBase)runConfiguration, processHandler, console); } } diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/XDebugSessionTab.java b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/XDebugSessionTab.java index d96830595c0b..efd760d40209 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/XDebugSessionTab.java +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/XDebugSessionTab.java @@ -198,7 +198,7 @@ public class XDebugSessionTab extends DebuggerSessionTabBase { if (env != null) { final RunProfile runConfiguration = env.getRunProfile(); registerFileMatcher(runConfiguration); - initLogConsoles(runConfiguration, myRunContentDescriptor.getProcessHandler()); + initLogConsoles(runConfiguration, myRunContentDescriptor.getProcessHandler(), myConsole); } rebuildViews(); diff --git a/plugins/junit/src/com/intellij/execution/junit/JUnitConfiguration.java b/plugins/junit/src/com/intellij/execution/junit/JUnitConfiguration.java index dd802ac01253..f83067678aa6 100644 --- a/plugins/junit/src/com/intellij/execution/junit/JUnitConfiguration.java +++ b/plugins/junit/src/com/intellij/execution/junit/JUnitConfiguration.java @@ -357,6 +357,11 @@ public class JUnitConfiguration extends ModuleBasedConfiguration Date: Fri, 21 Oct 2011 12:38:50 +0400 Subject: [PATCH 07/19] GRADLE-1856 Tooling API: Make it possible to get IntelliJ IDEA view to the gradle project Improved gradle api exceptions reporting --- .../wizard/GradleProjectImportBuilder.java | 15 +++- .../gradle/remote/GradleApiException.java | 85 +++++++++++++++++++ .../gradle/remote/GradleProjectResolver.java | 3 +- .../impl/GradleProjectResolverImpl.java | 70 ++++----------- 4 files changed, 117 insertions(+), 56 deletions(-) create mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/remote/GradleApiException.java diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/wizard/GradleProjectImportBuilder.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/wizard/GradleProjectImportBuilder.java index c6214087d1ac..048babe7168c 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/wizard/GradleProjectImportBuilder.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/wizard/GradleProjectImportBuilder.java @@ -17,6 +17,7 @@ import com.intellij.openapi.projectRoots.JavaSdkVersion; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.roots.ui.configuration.ModulesProvider; import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.packaging.artifacts.ModifiableArtifactModel; import com.intellij.projectImport.ProjectImportBuilder; import org.jetbrains.annotations.NotNull; @@ -27,6 +28,7 @@ import org.jetbrains.plugins.gradle.importing.model.GradleModule; import org.jetbrains.plugins.gradle.importing.model.GradleProject; import org.jetbrains.plugins.gradle.remote.GradleApiFacadeManager; import org.jetbrains.plugins.gradle.remote.GradleProjectResolver; +import org.jetbrains.plugins.gradle.remote.GradleApiException; import org.jetbrains.plugins.gradle.util.GradleBundle; import org.jetbrains.plugins.gradle.util.GradleIcons; import org.jetbrains.plugins.gradle.util.GradleLog; @@ -157,14 +159,21 @@ public class GradleProjectImportBuilder extends ProjectImportBuilder + * This class allows to extract textual description of the target problem and deliver it for further processing without risking to + * get the problems mentioned above. I.e. it doesn't require anything specific can be safely delivered to IJ process then. + * + * @author Denis Zhdanov + * @since 10/21/11 11:42 AM + */ +public class GradleApiException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + private final String myOriginalReason; + + public GradleApiException() { + this(null, null); + } + + public GradleApiException(@Nullable String message) { + this(message, null); + } + + public GradleApiException(@Nullable Throwable cause) { + this("", cause); + } + + public GradleApiException(@Nullable String message, @Nullable Throwable cause) { + super(extractMessage(message, cause)); + if (cause == null) { + myOriginalReason = ""; + return; + } + + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + try { + cause.printStackTrace(printWriter); + } + finally { + printWriter.close(); + } + myOriginalReason = stringWriter.toString(); + } + + /** + * @return textual description of the wrapped exception (if any); empty string otherwise + */ + @NotNull + public String getOriginalReason() { + return myOriginalReason; + } + + @Override + public void printStackTrace(PrintWriter s) { + super.printStackTrace(s); + s.println(myOriginalReason); + } + + @Override + public void printStackTrace(PrintStream s) { + super.printStackTrace(s); + s.println(myOriginalReason); + } + + @Nullable + private static String extractMessage(@Nullable String message, @Nullable Throwable cause) { + if (message != null) { + return message; + } + return cause == null ? "" : cause.getMessage(); + } + +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/GradleProjectResolver.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/GradleProjectResolver.java index ecb0a6324858..4c70a26380e7 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/GradleProjectResolver.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/GradleProjectResolver.java @@ -25,11 +25,12 @@ public interface GradleProjectResolver extends Remote { * @param downloadLibraries flag that specifies if third-party libraries that are not available locally should be resolved (downloaded) * @return object-level representation of the target gradle project * @throws RemoteException in case of unexpected exception during remote communications + * @throws GradleApiException in case of unexpected exception thrown from Gradle API * @throws IllegalArgumentException if given path doesn't point to directory that contains gradle project or if gradle api * returns invalid data * @throws IllegalStateException if it's not possible to resolve target project info */ @NotNull GradleProject resolveProjectInfo(@NotNull String projectPath, boolean downloadLibraries) - throws RemoteException, IllegalArgumentException, IllegalStateException; + throws RemoteException, GradleApiException, IllegalArgumentException, IllegalStateException; } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/impl/GradleProjectResolverImpl.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/impl/GradleProjectResolverImpl.java index 17dad9759b35..8b5ef5b420b2 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/impl/GradleProjectResolverImpl.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/impl/GradleProjectResolverImpl.java @@ -13,16 +13,13 @@ import org.gradle.tooling.model.idea.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.gradle.importing.model.*; -import org.jetbrains.plugins.gradle.remote.GradleProjectResolver; -import org.jetbrains.plugins.gradle.remote.RemoteGradleProcessSettings; -import org.jetbrains.plugins.gradle.remote.RemoteGradleService; +import org.jetbrains.plugins.gradle.remote.*; import org.jetbrains.plugins.gradle.util.GradleBundle; import org.jetbrains.plugins.gradle.util.GradleLog; import org.jetbrains.plugins.gradle.util.GradleUtil; import java.io.File; import java.rmi.RemoteException; -import java.util.Arrays; import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -37,16 +34,25 @@ import java.util.concurrent.atomic.AtomicReference; */ public class GradleProjectResolverImpl extends RemoteObject implements GradleProjectResolver, RemoteGradleService { - private static final Set NON_UNIQUE_PATH_ENTRIES = new HashSet(Arrays.asList( - "src", "main", "java", "test", "resources" - )); - - private final BlockingQueue myConnections = new LinkedBlockingQueue(); - private final AtomicReference mySettings = new AtomicReference(); - + private final BlockingQueue myConnections = new LinkedBlockingQueue(); + private final AtomicReference mySettings = new AtomicReference(); + private final GradleLibraryNamesMixer myLibraryNamesMixer = new GradleLibraryNamesMixer(); + @NotNull @Override public GradleProject resolveProjectInfo(@NotNull String projectPath, boolean downloadLibraries) + throws RemoteException, GradleApiException, IllegalArgumentException, IllegalStateException + { + try { + return doResolveProjectInfo(projectPath, downloadLibraries); + } + catch (Throwable e) { + throw new GradleApiException(e); + } + } + + @NotNull + private GradleProject doResolveProjectInfo(@NotNull String projectPath, boolean downloadLibraries) throws RemoteException, IllegalArgumentException, IllegalStateException { ProjectConnection connection = getConnection(projectPath); @@ -58,7 +64,7 @@ public class GradleProjectResolverImpl extends RemoteObject implements GradlePro // populating dependent module object. Map> modules = createModules(project, result); populateModules(modules.values(), result); - diversifyLibraryNamesIfNecessary(result); + myLibraryNamesMixer.mixNames(result.getLibraries()); return result; } @@ -315,46 +321,6 @@ public class GradleProjectResolverImpl extends RemoteObject implements GradlePro return null; } - /** - * Gradle API doesn't provide library names at the moment, so, we deduce them from the path. However, it's possible to have identical - * names then. - *

- * This method solves that by diversifying duplicate library names. - * - * @param project target project which library names should be diversified if necessary - */ - private static void diversifyLibraryNamesIfNecessary(@NotNull GradleProject project) { - Map libraries = new HashMap(); - for (GradleLibrary library : project.getLibraries()) { - GradleLibrary previous = libraries.remove(library.getName()); - if (previous == null) { - libraries.put(library.getName(), library); - continue; - } - previous.setName(generateName(previous)); - libraries.put(previous.getName(), previous); - library.setName(generateName(library)); - libraries.put(library.getName(), library); - } - } - - @NotNull - private static String generateName(@NotNull GradleLibrary library) { - for (LibraryPathType pathType : LibraryPathType.values()) { - String path = library.getPath(pathType); - if (path == null) { - continue; - } - File file = new File(path).getParentFile(); - for (; file != null; file = file.getParentFile()) { - if (!NON_UNIQUE_PATH_ENTRIES.contains(file.getName())) { - return file.getName() + "-" + library.getName(); - } - } - } - return library.getName(); - } - /** * Allows to retrieve gradle api connection to use for the given project. * From 71147efe86fc05281d91cbed72c4ba170d2b58f8 Mon Sep 17 00:00:00 2001 From: "Denis.Zhdanov" Date: Fri, 21 Oct 2011 13:26:02 +0400 Subject: [PATCH 08/19] IDEA-75727 Gradle: support library dependencies that don't point to *.jar files --- .../importing/GradleModulesImporter.java | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/GradleModulesImporter.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/GradleModulesImporter.java index 517e6e777f1e..39398cf04945 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/GradleModulesImporter.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/GradleModulesImporter.java @@ -441,14 +441,19 @@ public class GradleModulesImporter { GradleLog.LOG.warn(String.format("Can't find %s of the library '%s' at path '%s'", pathType, gradleLibrary.getName(), path)); continue; } - VirtualFile jarRoot = JarFileSystem.getInstance().getJarRootForLocalFile(virtualFile); - if (jarRoot == null) { - GradleLog.LOG.warn(String.format( - "Can't parse contents of the jar file at path '%s' for the library '%s''", path, gradleLibrary.getName() - )); - continue; + if (virtualFile.isDirectory()) { + model.addRoot(virtualFile, pathType.getRootType()); + } + else { + VirtualFile jarRoot = JarFileSystem.getInstance().getJarRootForLocalFile(virtualFile); + if (jarRoot == null) { + GradleLog.LOG.warn(String.format( + "Can't parse contents of the jar file at path '%s' for the library '%s''", path, gradleLibrary.getName() + )); + continue; + } + model.addRoot(jarRoot, pathType.getRootType()); } - model.addRoot(jarRoot, pathType.getRootType()); } } } From c18aee77d6df9906f3dbcca65d365d52696d364f Mon Sep 17 00:00:00 2001 From: "Denis.Zhdanov" Date: Fri, 21 Oct 2011 13:27:22 +0400 Subject: [PATCH 09/19] IDEA-75728 Gradle: Try to ensure that libraries of the imported project have distinct names --- .../model/AbstractGradleDependency.java | 14 +- .../remote/impl/GradleLibraryNamesMixer.java | 222 ++++++++++++++++++ .../impl/GradleLibraryNamesMixerTest.java | 71 ++++++ 3 files changed, 295 insertions(+), 12 deletions(-) create mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/remote/impl/GradleLibraryNamesMixer.java create mode 100644 plugins/gradle/testSources/org/jetbrains/plugins/gradle/remote/impl/GradleLibraryNamesMixerTest.java diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/AbstractGradleDependency.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/AbstractGradleDependency.java index 1a4538dc187f..5e26f0accf18 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/AbstractGradleDependency.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/model/AbstractGradleDependency.java @@ -35,22 +35,12 @@ public abstract class AbstractGradleDependency extends AbstractGradleEntity impl @Override public int hashCode() { - int result = myScope.hashCode(); - result = 31 * result + (myExported ? 1 : 0); - return result; + return 31; } @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - AbstractGradleDependency that = (AbstractGradleDependency)o; - - if (myExported != that.myExported) return false; - if (myScope != that.myScope) return false; - - return true; + return this == o || (o != null && getClass() == o.getClass()); } @Override diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/impl/GradleLibraryNamesMixer.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/impl/GradleLibraryNamesMixer.java new file mode 100644 index 000000000000..45413cdf04e1 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/impl/GradleLibraryNamesMixer.java @@ -0,0 +1,222 @@ +package org.jetbrains.plugins.gradle.remote.impl; + +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.containers.HashMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.gradle.importing.model.GradleLibrary; +import org.jetbrains.plugins.gradle.importing.model.LibraryPathType; + +import java.io.File; +import java.util.*; + +/** + * Encapsulates logic of checking if particular collection of gradle libraries contains libraries with the same names and + * tries to diversify them in the case of the positive answer. + *

+ * Thread-safe. + * + * @author Denis Zhdanov + * @since 10/19/11 2:04 PM + */ +public class GradleLibraryNamesMixer { + + /** + * Holds mappings like ('file name'; boolean) where 'file name' defines 'too common' file/dir + * name that should not be used during library name generation. Boolean flag indicates if 'common file name' may be used + * if 'non-common' files are the same. + *

+ * Example: consider the following file system tree: + *

+   *   module
+   *     |_src
+   *        |_main
+   *        |  |_resources
+   *        |
+   *        |_test
+   *           |_resources
+   * 
+ * Let's say we have two libraries where one of them points to 'src/main/resources' and another one + * to 'src/test/resources'. We want to generate names 'module-resources' and + * 'module-test-resources' respectively because 'test' entry at the current collection is + * stored with 'true' flag. + */ + private static final Map NON_UNIQUE_PATH_ENTRIES = new HashMap(); + static { + NON_UNIQUE_PATH_ENTRIES.put("src", false); + NON_UNIQUE_PATH_ENTRIES.put("main", false); + NON_UNIQUE_PATH_ENTRIES.put("test", true); + NON_UNIQUE_PATH_ENTRIES.put("resources", false); + NON_UNIQUE_PATH_ENTRIES.put("java", false); + NON_UNIQUE_PATH_ENTRIES.put("groovy", false); + } + private static final char NAME_SEPARATOR = '-'; + + /** + * Tries to ensure that given libraries have distinct names, i.e. traverses all of them and tries to generate + * unique name for those with equal names. + * + * @param libraries libraries to process + */ + @SuppressWarnings("MethodMayBeStatic") + public void mixNames(@NotNull Iterable libraries) { + Map names = new HashMap(); + List data = new ArrayList(); + for (GradleLibrary library : libraries) { + Wrapped wrapped = new Wrapped(library); + data.add(wrapped); + } + boolean mixed = false; + while (!mixed) { + mixed = doMixNames(data, names); + } + } + + /** + * Does the same as {@link #mixNames(Iterable)} but uses given ('library name; wrapped library'} mappings cache. + * + * @param libraries libraries to process + * @param cache cache to use + * @return true if all of the given libraries have distinct names now; false otherwise + */ + private static boolean doMixNames(@NotNull Collection libraries, @NotNull Map cache) { + cache.clear(); + for (Wrapped current : libraries) { + Wrapped previous = cache.remove(current.library.getName()); + if (previous == null) { + cache.put(current.library.getName(), current); + } + else { + mixNames(current, previous); + return current.library.getName().equals(previous.library.getName()); // Stop processing if it's not possible to generate + } + } + return true; + } + + /** + * Tries to generate distinct names for the given wrapped libraries (assuming that they have equal names at the moment). + * + * @param wrapped1 one of the libraries with equal names + * @param wrapped2 another library which name is equal to the name of the given one + */ + @SuppressWarnings("AssignmentToForLoopParameter") + private static void mixNames(@NotNull Wrapped wrapped1, @NotNull Wrapped wrapped2) { + if (!wrapped1.prepare() || !wrapped2.prepare()) { + return; + } + String wrapped1AltText = null; + String wrapped2AltText = null; + + for (File file1 = wrapped1.currentFile, file2 = wrapped2.currentFile; + file1 != null && file2 != null; + file1 = file1.getParentFile(), file2 = file2.getParentFile()) + { + while (file1 != null && !StringUtil.isEmpty(file1.getName()) && NON_UNIQUE_PATH_ENTRIES.containsKey(file1.getName())) { + if (NON_UNIQUE_PATH_ENTRIES.get(file1.getName())) { + if (StringUtil.isEmpty(wrapped1AltText)) { + wrapped1AltText = file1.getName(); + } + else { + wrapped1AltText += NAME_SEPARATOR + file1.getName(); + } + } + file1 = file1.getParentFile(); + } + while (file2 != null && !StringUtil.isEmpty(file2.getName()) && NON_UNIQUE_PATH_ENTRIES.containsKey(file2.getName())) { + if (NON_UNIQUE_PATH_ENTRIES.get(file2.getName())) { + if (StringUtil.isEmpty(wrapped2AltText)) { + wrapped2AltText = file2.getName(); + } + else { + wrapped2AltText += NAME_SEPARATOR + file2.getName(); + } + } + file2 = file2.getParentFile(); + } + + if (file1 == null) { + wrapped1.nextFile(); + } + if (file2 == null) { + wrapped2.nextFile(); + } + + if (file1 != null && file2 != null && !file1.getName().equals(file2.getName())) { + wrapped1.library.setName(file1.getName() + NAME_SEPARATOR + wrapped1.library.getName()); + wrapped2.library.setName(file2.getName() + NAME_SEPARATOR + wrapped2.library.getName()); + } + + if (wrapped1.library.getName().equals(wrapped2.library.getName())) { + if (wrapped1AltText != null) { + diversifyName(wrapped1AltText, wrapped1, file1); + return; + } + else if (wrapped2AltText != null) { + diversifyName(wrapped2AltText, wrapped2, file1); + return; + } + } + else { + return; + } + + if (file1 == null || file2 == null) { + return; + } + } + } + + @SuppressWarnings("ConstantConditions") + private static void diversifyName(@NotNull String changeText, @NotNull Wrapped wrapped, @Nullable File file) { + String name = wrapped.library.getName(); + int i = file == null ? - 1 : name.indexOf(file.getName()); + final String newName; + if (i >= 0) { + newName = name.substring(0, i + file.getName().length()) + NAME_SEPARATOR + changeText + name.substring(i + file.getName().length()); + } + else { + newName = changeText + NAME_SEPARATOR + name; + } + wrapped.library.setName(newName); + } + + /** + * Wraps target library and hold auxiliary information required for the processing. + */ + private static class Wrapped { + /** Holds list of files that may be used for name generation. */ + public final Set files = new HashSet(); + /** File that was used for the current name generation. */ + public File currentFile; + /** Target library. */ + public GradleLibrary library; + + Wrapped(@NotNull GradleLibrary library) { + this.library = library; + for (LibraryPathType pathType : LibraryPathType.values()) { + String path = library.getPath(pathType); + if (path != null) { + files.add(new File(path)); + } + } + } + + public boolean prepare() { + if (currentFile != null) { + return true; + } + return nextFile(); + } + + public boolean nextFile() { + if (files.isEmpty()) { + return false; + } + Iterator iterator = files.iterator(); + currentFile = iterator.next(); + iterator.remove(); + return true; + } + } +} diff --git a/plugins/gradle/testSources/org/jetbrains/plugins/gradle/remote/impl/GradleLibraryNamesMixerTest.java b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/remote/impl/GradleLibraryNamesMixerTest.java new file mode 100644 index 000000000000..dc5dc5077934 --- /dev/null +++ b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/remote/impl/GradleLibraryNamesMixerTest.java @@ -0,0 +1,71 @@ +package org.jetbrains.plugins.gradle.remote.impl; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.gradle.importing.model.GradleLibrary; +import org.jetbrains.plugins.gradle.importing.model.LibraryPathType; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; + +/** + * @author Denis Zhdanov + * @since 10/19/11 5:25 PM + */ +public class GradleLibraryNamesMixerTest { + + private GradleLibraryNamesMixer myMixer; + + @Before + public void setUp() { + myMixer = new GradleLibraryNamesMixer(); + } + + @Test + public void sourceVsTest() { + doTest( + t("resources", "my-module-resources", "dir1/dir2/my-module/src/main/resources"), + t("resources", "my-module-test-resources", "dir1/dir2/my-module/src/test/resources"), + t("resources", "my-another-module-resources", "dir1/dir2/my-another-module/src/main/resources"), + t("resources", "my-another-module-test-resources", "dir1/dir2/my-another-module/src/test/resources") + ); + } + + private void doTest(TestDataEntry... entries) { + Map expected = new IdentityHashMap(); + List libraries = new ArrayList(); + for (TestDataEntry entry : entries) { + GradleLibrary library = new GradleLibrary(entry.initialName); + library.addPath(LibraryPathType.BINARY, entry.path); + libraries.add(library); + expected.put(library, entry.expectedName); + } + + myMixer.mixNames(libraries); + for (GradleLibrary library : libraries) { + assertEquals(expected.get(library), library.getName()); + } + } + + private static class TestDataEntry { + + public String initialName; + public String expectedName; + public String path; + + TestDataEntry(@NotNull String initialName, @NotNull String expectedName, @NotNull String path) { + this.initialName = initialName; + this.expectedName = expectedName; + this.path = path; + } + } + + public static TestDataEntry t(@NotNull String initialName, @NotNull String expectedName, @NotNull String path) { + return new TestDataEntry(initialName, expectedName, path); + } +} From 9de4c9d2f0d9ae3ccc7efe85871b0b8e300323c5 Mon Sep 17 00:00:00 2001 From: "Denis.Zhdanov" Date: Fri, 21 Oct 2011 15:17:05 +0400 Subject: [PATCH 10/19] IDEA-75560 UnmarshalException while importing a Gradle project in idea-IC-110.3 Using explicit timeout (one hour) for RMI sockets. Reason: default timeout is fifteen seconds and it was often exceeded when gradle api resolved external dependencies --- .../plugins/gradle/remote/GradleApiFacadeManager.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/GradleApiFacadeManager.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/GradleApiFacadeManager.java index 0d5e0994dce9..a183c4e8760f 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/GradleApiFacadeManager.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/GradleApiFacadeManager.java @@ -43,6 +43,7 @@ import java.nio.charset.Charset; import java.rmi.RemoteException; import java.util.ArrayList; import java.util.Collection; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; /** @@ -131,6 +132,13 @@ public class GradleApiFacadeManager { params.setMainClass(MAIN_CLASS_NAME); params.getVMParametersList().addParametersString("-Djava.awt.headless=true -Xmx512m"); + + // It may take a while for gradle api to resolve external dependencies. Default RMI timeout + // is 15 seconds (http://download.oracle.com/javase/1.4.2/docs/guide/rmi/sunrmiproperties.html#connectionTimeout), + // we don't want to get EOFException because of that. + params.getVMParametersList().addParametersString( + "-Dsun.rmi.transport.connectionTimeout=" + String.valueOf(TimeUnit.HOURS.toMillis(1)) + ); //params.getVMParametersList().addParametersString("-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5009"); return params; } From ed7c84b28321f7cd6acc2f4bd984a6d7ff72e613 Mon Sep 17 00:00:00 2001 From: irengrig Date: Fri, 21 Oct 2011 15:24:17 +0400 Subject: [PATCH 11/19] git log: increase cache size --- .../src/git4idea/history/wholeTree/DetailsCache.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/DetailsCache.java b/plugins/git4idea/src/git4idea/history/wholeTree/DetailsCache.java index 245077d8ebf7..ed14b335101e 100644 --- a/plugins/git4idea/src/git4idea/history/wholeTree/DetailsCache.java +++ b/plugins/git4idea/src/git4idea/history/wholeTree/DetailsCache.java @@ -18,7 +18,6 @@ import com.intellij.openapi.progress.BackgroundTaskQueue; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Getter; import com.intellij.openapi.util.Pair; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.changes.BackgroundFromStartOption; @@ -42,7 +41,7 @@ import java.util.Map; * @author irengrig */ public class DetailsCache { - private final static int ourSize = 400; + private final static int ourSize = 500; private final SLRUMap, GitCommit> myCache; private final SLRUMap, List> myBranches; private final Project myProject; @@ -64,8 +63,8 @@ public class DetailsCache { myStash = new HashMap>(); myRefresh = uiRefresh; myLock = new Object(); - myCache = new SLRUMap, GitCommit>(ourSize, 50); - myBranches = new SLRUMap, List>(10, 10); + myCache = new SLRUMap, GitCommit>(ourSize, 150); + myBranches = new SLRUMap, List>(20, 20); } public GitCommit convert(final VirtualFile root, final AbstractHash hash) { From 3760ab4c7efa0e5df96cfa0918f154a80f88c057 Mon Sep 17 00:00:00 2001 From: Dmitry Avdeev Date: Fri, 21 Oct 2011 15:23:13 +0400 Subject: [PATCH 12/19] inspections language --- plugins/properties/src/META-INF/plugin.xml | 8 ++++---- plugins/testng/src/META-INF/plugin.xml | 14 +++++++------- plugins/xpath/xpath-view/src/META-INF/plugin.xml | 10 +++++----- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/plugins/properties/src/META-INF/plugin.xml b/plugins/properties/src/META-INF/plugin.xml index 1a4c89778c82..6d9161c51233 100644 --- a/plugins/properties/src/META-INF/plugin.xml +++ b/plugins/properties/src/META-INF/plugin.xml @@ -46,15 +46,15 @@ - - diff --git a/plugins/testng/src/META-INF/plugin.xml b/plugins/testng/src/META-INF/plugin.xml index 061da3328313..8e67df9c8566 100644 --- a/plugins/testng/src/META-INF/plugin.xml +++ b/plugins/testng/src/META-INF/plugin.xml @@ -15,22 +15,22 @@ - - - - - - - diff --git a/plugins/xpath/xpath-view/src/META-INF/plugin.xml b/plugins/xpath/xpath-view/src/META-INF/plugin.xml index 77d163f391f5..8a85d90d12c1 100644 --- a/plugins/xpath/xpath-view/src/META-INF/plugin.xml +++ b/plugins/xpath/xpath-view/src/META-INF/plugin.xml @@ -83,15 +83,15 @@ - - - - - From 60210d79199f6add144945978410abc5ab99aada Mon Sep 17 00:00:00 2001 From: irengrig Date: Fri, 21 Oct 2011 15:35:27 +0400 Subject: [PATCH 13/19] git log tree --- .../intellij/openapi/vcs/GroupingMerger.java | 9 +- .../history/wholeTree/BigTableTableModel.java | 256 ++++++++++---- .../history/wholeTree/GitLogSettings.java | 9 + .../git4idea/history/wholeTree/GitLogUI.java | 129 ++++++- .../GitTableScrollChangeListener.java | 5 +- .../history/wholeTree/GraphGutter.java | 320 +++++++++++++----- .../history/wholeTree/SkeletonBuilder.java | 110 +----- .../history/wholeTree/TreeNavigationImpl.java | 24 +- .../git4idea/history/wholeTree/WireEvent.java | 8 + .../history/wholeTree/WireEventsIterator.java | 34 ++ .../git4idea/tests/GroupingMergerTest.java | 102 ++++++ 11 files changed, 721 insertions(+), 285 deletions(-) create mode 100644 plugins/git4idea/src/git4idea/history/wholeTree/WireEventsIterator.java create mode 100644 plugins/git4idea/tests/git4idea/tests/GroupingMergerTest.java diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/GroupingMerger.java b/platform/vcs-api/src/com/intellij/openapi/vcs/GroupingMerger.java index a5a99b06d9c5..a5c281e8a809 100644 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/GroupingMerger.java +++ b/platform/vcs-api/src/com/intellij/openapi/vcs/GroupingMerger.java @@ -55,7 +55,7 @@ public abstract class GroupingMerger { // for group headers to not be left alone without its group if (idx > 0 && (! filter(first.get(idx - 1)))) { -- idx; - if (idx > 0) --idx; + //if (idx > 0) --idx; // todo whether its ok } final ReadonlyList remergePart = first.cut(idx); if (idx > 0) { @@ -67,7 +67,7 @@ public abstract class GroupingMerger { public void consume(T t, Integer integer) { doForGroup(t, first); first.add(t); - int was = integer + finalIdx + 1; + int was = integer + finalIdx; //System.out.println("was " + integer + "became " + (first.getSize() - 1)); oldBecame(was, first.getSize() - 1); } @@ -78,7 +78,6 @@ public abstract class GroupingMerger { final T wrapped = wrapItem(t); first.add(wrapped); - // todo should more effectively compute size!!! afterConsumed(wrapped, first.getSize() - 1); } }); @@ -87,14 +86,12 @@ public abstract class GroupingMerger { private void doForGroup(T t, StepList first) { final S newGroup = getGroup(t); - if (! Comparing.equal(newGroup, myCurrentGroup)) { + if (newGroup != null && ! Comparing.equal(newGroup, myCurrentGroup)) { first.add(wrapGroup(newGroup, t)); myCurrentGroup = newGroup; } } - // todo here I doubt very much abt comparator usage, but seems like it doesn't reorder items from same repo - // todo which is essential public void merge(final ReadonlyList one, final ReadonlyList two, final Comparator comparator, diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/BigTableTableModel.java b/plugins/git4idea/src/git4idea/history/wholeTree/BigTableTableModel.java index 91f144baa45f..8e81d6d547fb 100644 --- a/plugins/git4idea/src/git4idea/history/wholeTree/BigTableTableModel.java +++ b/plugins/git4idea/src/git4idea/history/wholeTree/BigTableTableModel.java @@ -20,9 +20,7 @@ import com.intellij.openapi.vcs.BigArray; import com.intellij.openapi.vcs.GroupingMerger; import com.intellij.openapi.vcs.changes.committed.DateChangeListGroupingStrategy; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.util.containers.MultiMap; -import com.intellij.util.containers.ReadonlyList; -import com.intellij.util.containers.StepList; +import com.intellij.util.containers.*; import com.intellij.util.ui.ColumnInfo; import git4idea.history.browser.GitCommit; import org.jetbrains.annotations.NotNull; @@ -30,6 +28,7 @@ import org.jetbrains.annotations.Nullable; import javax.swing.table.AbstractTableModel; import java.util.*; +import java.util.HashMap; /** * @author irengrig @@ -45,6 +44,12 @@ public class BigTableTableModel extends AbstractTableModel { private List myOrder; private Map myAdditions; // end group + + private final BidirectionalMap myIdxMap; + // for faster drawing + private final Map> myRepoIdxMap; + // index of NEXT + private final Map myRunningRepoIdxs; @NotNull private final List myColumns; @@ -63,6 +68,10 @@ public class BigTableTableModel extends AbstractTableModel { public BigTableTableModel(@NotNull final List columns, Runnable init) { myColumns = columns; myInit = init; + myIdxMap = new BidirectionalMap(); + myRunningRepoIdxs = new HashMap(); + myRepoIdxMap = new HashMap>(); + myCurrentComparator = CommitIReorderingInsideOneRepoComparator.getInstance(); final DateChangeListGroupingStrategy delegate = new DateChangeListGroupingStrategy(); myStrategy = new CommitGroupingStrategy() { @@ -131,8 +140,8 @@ public class BigTableTableModel extends AbstractTableModel { public int getTotalWires() { if (mySkeletonBuilder == null) return -1; int wires = 0; - for (TreeNavigationImpl navigation : myNavigation.values()) { - wires += navigation.getMaximumWires(); + for (SkeletonBuilder skeletonBuilder : mySkeletonBuilder.values()) { + wires += skeletonBuilder.getMaxWireNum(); } return wires; } @@ -142,7 +151,7 @@ public class BigTableTableModel extends AbstractTableModel { if (mySkeletonBuilder == null) return null; final List result = new ArrayList(myOrder.size()); for (VirtualFile file : myOrder) { - result.add(myNavigation.get(file).getMaximumWires()); + result.add(mySkeletonBuilder.get(file).getMaxWireNum()); } return result; } @@ -153,32 +162,109 @@ public class BigTableTableModel extends AbstractTableModel { return myAdditions.get(file) + commitI.getWireNumber(); } - public WiresGroupIterator getGroupIterator(final int firstRow) { - return new WiresGroupIterator(firstRow); + public Map getGroupIterators(final int firstRow) { + final Map map = new HashMap(); + for (VirtualFile virtualFile : mySkeletonBuilder.keySet()) { + map.put(virtualFile, new WiresGroupIterator(firstRow, virtualFile)); + } + return map; } - class WiresGroupIterator { + class WiresGroupIterator implements WireEventsIterator { private final int myFirstIdx; private List myFirstUsed; + private final VirtualFile myRoot; + private final int myOffset; + private final Iterator myWireEventsIterator; + private Integer myFloor; - WiresGroupIterator(int firstIdx) { + WiresGroupIterator(int firstIdx, VirtualFile root) { myFirstIdx = firstIdx; + myRoot = root; + myOffset = myAdditions.get(myRoot); + myFirstUsed = new ArrayList(); - for (VirtualFile file : myOrder) { - TreeNavigationImpl navigation = myNavigation.get(file); - final List used = navigation.getUsedWires(firstIdx, myLines, mySkeletonBuilder.get(file).getFutureConvertor()).getUsed(); - myFirstUsed.addAll(used); + TreeNavigationImpl navigation = myNavigation.get(myRoot); + // get less idx + myFloor = myRepoIdxMap.get(myRoot).floor(firstIdx); + int idx; + final ReadonlyList wrapper = createWrapper(myRoot); + if (myFloor == null) { + myFloor = 0; + idx = 0; + } else { + final List keysByValue = myIdxMap.getKeysByValue(myFloor); + idx = -1; + for (InnerIdx innerIdx : keysByValue) { + if (innerIdx.getRoot().equals(myRoot)) { + idx = innerIdx.getInsideRepoIdx(); + break; + } + } + assert idx != -1; } + final List used = navigation.getUsedWires(idx, wrapper, mySkeletonBuilder.get(myRoot).getFutureConvertor()).getUsed(); + for (Integer integer : used) { + myFirstUsed.add(integer + myOffset); + } + myWireEventsIterator = navigation.createWireEventsIterator(idx); } + @Override + public Integer getFloor() { + return myFloor; + } + + @Override + public Iterator getWireEventsIterator() { + return new Iterator() { + @Override + public boolean hasNext() { + return myWireEventsIterator.hasNext(); + } + + @Override + public WireEvent next() { + final WireEvent next = myWireEventsIterator.next(); + final Convertor innerToOuter = new Convertor() { + @Override + public Integer convert(Integer o) { + if (o == -1) return -1; + final int insideRepoIdx = o.intValue(); + final Integer integer = myIdxMap.get(new InnerIdx(myRoot, insideRepoIdx)); + assert integer != null; + return integer; + } + }; + final Convertor arraysConvertor = new Convertor() { + @Override + public int[] convert(int[] o) { + if (o == null) return null; + final int[] result = new int[o.length]; + for (int i = 0; i < o.length; i++) { + int i1 = o[i]; + result[i] = innerToOuter.convert(i1); + } + return result; + } + }; + final WireEvent wireEvent = new WireEvent(innerToOuter.convert(next.getCommitIdx()), arraysConvertor.convert(next.getCommitsEnds())); + wireEvent.setCommitsStarts(arraysConvertor.convert(next.getCommitsStarts())); + wireEvent.setWireEnds(arraysConvertor.convert(next.getWireEnds())); + return wireEvent; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + @Override public List getFirstUsed() { return myFirstUsed; } - - public WireEvent getEventForRow(final int row) { - assert row >= myFirstIdx; - return myNavigation.get(getCommitAt(row).selectRepository(myRootsHolder.getRoots())).getEventForRow(row); - } } @Override @@ -211,8 +297,7 @@ public class BigTableTableModel extends AbstractTableModel { } public void clear(boolean noFilters) { - // todo uncomment for git log tree - /*if (noFilters) { + if (noFilters) { myCurrentComparator = CommitIComparator.getInstance(); myNavigation = new HashMap(); mySkeletonBuilder = new HashMap(); @@ -225,15 +310,19 @@ public class BigTableTableModel extends AbstractTableModel { myNavigation.put(vf, navigation); mySkeletonBuilder.put(vf, skeletonBuilder); myAdditions.put(vf, 0); + myRepoIdxMap.put(vf, new TreeSet()); + + myRunningRepoIdxs.put(vf, 0); + myIdxMap.clear(); } } else { myCurrentComparator = CommitIReorderingInsideOneRepoComparator.getInstance(); - */ + myAdditions = null; mySkeletonBuilder = null; myNavigation = null; myOrder = null; - //} + } myLines = new BigArray(10); myCutCount = -1; } @@ -251,17 +340,18 @@ public class BigTableTableModel extends AbstractTableModel { Collections.sort(lines, myCurrentComparator); } - final Integer[] parentsIdx = new Integer[1]; - parentsIdx[0] = 0; - - final Set whatToRecount = mySkeletonBuilder == null ? null : new HashSet(); - final Map indexRecalculation = new HashMap(); myStrategy.beforeStart(); // find those ..... long awaited start idx by stupid long iteration since // items can NOT be ordered by simple rule + final int[] parentsIdx = new int[1]; + parentsIdx[0] = 0; int idxFrom = findIdx(lines); + final CommitI commitI = lines.get(0); + final VirtualFile listRoot = commitI.selectRepository(myRootsHolder.getRoots()); + final ReadonlyList wrapperList = createWrapper(listRoot); + int recountFrom = new GroupingMerger() { @Override protected CommitI wrapItem(CommitI commitI) { @@ -274,9 +364,14 @@ public class BigTableTableModel extends AbstractTableModel { @Override protected void afterConsumed(CommitI commitI, int i) { if (mySkeletonBuilder != null && ! commitI.holdsDecoration()) { - whatToRecount.add(i); - //mySkeletonBuilder.get(commitI.selectRepository(myRootsHolder.getRoots())).consume(commitI, parents.get(parentsIdx[0]), myLines, i); - //++parentsIdx[0]; + final VirtualFile root = commitI.selectRepository(myRootsHolder.getRoots()); + final Integer innerIdx = myRunningRepoIdxs.get(root); + myIdxMap.put(new InnerIdx(root, innerIdx), i); + myRepoIdxMap.get(root).add(i); + myRunningRepoIdxs.put(root, innerIdx + 1); + + mySkeletonBuilder.get(root).consume(commitI, parents.get(parentsIdx[0]), wrapperList, innerIdx); + ++ parentsIdx[0]; } } @@ -287,7 +382,7 @@ public class BigTableTableModel extends AbstractTableModel { @Override protected String getGroup(CommitI commitI) { - return mySkeletonBuilder != null ? "" : myStrategy.getGroupName(commitI); + return mySkeletonBuilder != null ? null : myStrategy.getGroupName(commitI); } @Override @@ -298,42 +393,50 @@ public class BigTableTableModel extends AbstractTableModel { @Override protected void oldBecame(int was, int is) { if (mySkeletonBuilder != null && was != is) { - indexRecalculation.put(was, is); - /*CommitI commitI = myLines.get(is); - if (! commitI.holdsDecoration()) { - mySkeletonBuilder.get(commitI.selectRepository(myRootsHolder.getRoots())).oldBecameNew(was, is); - }*/ + final List keys = myIdxMap.getKeysByValue(was); + final VirtualFile root = myLines.get(is).selectRepository(myRootsHolder.getRoots()); + assert ! root.equals(listRoot); + myRepoIdxMap.get(root).remove(was); + myRepoIdxMap.get(root).add(is); + + InnerIdx found = null; + for (InnerIdx key : keys) { + if (key.getRoot().equals(root)) { + found = key; + break; + } + } + assert found != null; + myIdxMap.put(found, is); } - // todo - //System.out.println("old: " + was + " became: " + is); } }.firstPlusSecond(myLines, new ReadonlyList.ArrayListWrapper(lines), myCurrentComparator, mySkeletonBuilder == null ? -1 : idxFrom); if (mySkeletonBuilder != null) { - for (SkeletonBuilder skeletonBuilder : mySkeletonBuilder.values()) { - skeletonBuilder.oldBecameNew(indexRecalculation); - } + myNavigation.get(listRoot).recalcIndex(wrapperList, mySkeletonBuilder.get(listRoot).getFutureConvertor()); - for (int i = recountFrom; i < myLines.getSize(); i++) { - final CommitI commitI = myLines.get(i); - if (mySkeletonBuilder != null && ! commitI.holdsDecoration() && whatToRecount.contains(i)) { - mySkeletonBuilder.get(commitI.selectRepository(myRootsHolder.getRoots())).consume(commitI, parents.get(parentsIdx[0]), myLines, i); - ++parentsIdx[0]; - } - } - - for (Map.Entry entry : myNavigation.entrySet()) { - final TreeNavigationImpl navigation = myNavigation.get(entry.getKey()); - navigation.recalcIndex(myLines, mySkeletonBuilder.get(entry.getKey()).getFutureConvertor()); - } int size = 0; for (VirtualFile file : myOrder) { myAdditions.put(file, size); - size += myNavigation.get(file).getMaximumWires(); + size += mySkeletonBuilder.get(file).getMaxWireNum(); } } } + private ReadonlyList createWrapper(final VirtualFile root) { + return new ReadonlyList() { + @Override + public CommitI get(int idx) { + return myLines.get(myIdxMap.get(new InnerIdx(root, idx))); + } + + @Override + public int getSize() { + return myRunningRepoIdxs.get(root); + } + }; + } + private int findIdx(List lines) { final VirtualFile targetRepo = lines.get(0).selectRepository(myRootsHolder.getRoots()); final long time = lines.get(0).getTime(); @@ -344,7 +447,7 @@ public class BigTableTableModel extends AbstractTableModel { return i + 1; // will be equal to list size sometimes, is that ok? } else { if (current.getTime() > time) { - return i + 1; + return i; } } } @@ -363,7 +466,6 @@ public class BigTableTableModel extends AbstractTableModel { myStrategy = strategy; } - // todo test public void printNavigation() { for (Map.Entry entry : myNavigation.entrySet()) { if (entry.getKey().getPath().contains("inner")) { @@ -371,4 +473,42 @@ public class BigTableTableModel extends AbstractTableModel { } } } -} + + public static class InnerIdx { + private final VirtualFile myRoot; + private final int myInsideRepoIdx; + + public InnerIdx(VirtualFile root, int insideRepoIdx) { + myRoot = root; + myInsideRepoIdx = insideRepoIdx; + } + + public VirtualFile getRoot() { + return myRoot; + } + + public int getInsideRepoIdx() { + return myInsideRepoIdx; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + InnerIdx innerIdx = (InnerIdx)o; + + if (myInsideRepoIdx != innerIdx.myInsideRepoIdx) return false; + if (!myRoot.equals(innerIdx.myRoot)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = myRoot.hashCode(); + result = 31 * result + myInsideRepoIdx; + return result; + } + } +} \ No newline at end of file diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/GitLogSettings.java b/plugins/git4idea/src/git4idea/history/wholeTree/GitLogSettings.java index 34e9580e1769..237aab52a026 100644 --- a/plugins/git4idea/src/git4idea/history/wholeTree/GitLogSettings.java +++ b/plugins/git4idea/src/git4idea/history/wholeTree/GitLogSettings.java @@ -61,6 +61,7 @@ public class GitLogSettings implements PersistentStateComponent filter public boolean myHighlight = true; + public boolean myShowTree = true; } @Override @@ -72,6 +73,14 @@ public class GitLogSettings implements PersistentStateComponent myRefs; private final SymbolicRefs myRecalculatedCommon; private UIRefresh myUIRefresh; - private JBTable myJBTable; + private MouseOpenJBTable myJBTable; private GraphGutter myGraphGutter; private RepositoryChangesBrowser myRepositoryChangesBrowser; final List myCommitsInRepositoryChangesBrowser; @@ -125,6 +128,9 @@ public class GitLogUI implements Disposable { private final TableCellRenderer myAuthorRenderer; private MyRootsAction myRootsAction; + private JPanel myEqualToHeadr; + private boolean myThereAreFilters; + private final GitLogUI.MyShowTreeAction myMyShowTreeAction; public GitLogUI(Project project, final Mediator mediator) { myProject = project; @@ -154,6 +160,7 @@ public class GitLogUI implements Disposable { initUiRefresh(); myAuthorRenderer = new HighLightingRenderer(HIGHLIGHT_TEXT_ATTRIBUTES, SimpleTextAttributes.REGULAR_ATTRIBUTES); + myMyShowTreeAction = new MyShowTreeAction(); } public void initFromSettings() { @@ -247,6 +254,8 @@ public class GitLogUI implements Disposable { myDataBeingAdded = false; myJBTable.revalidate(); myJBTable.repaint(); + ((JComponent) myEqualToHeadr.getParent()).revalidate(); + myEqualToHeadr.getParent().repaint(); } private void start() { @@ -423,6 +432,7 @@ public class GitLogUI implements Disposable { myJBTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { + myGraphGutter.getComponent().repaint(); mySelectionRequestsMerger.request(); } }); @@ -572,7 +582,7 @@ public class GitLogUI implements Disposable { } private JPanel createMainTable() { - myJBTable = new JBTable(myTableModel) { + myJBTable = new MouseOpenJBTable(myTableModel) { @Override public TableCellRenderer getCellRenderer(int row, int column) { final TableCellRenderer custom = myTableModel.getColumnInfo(column).getRenderer(myTableModel.getValueAt(row, column)); @@ -596,10 +606,11 @@ public class GitLogUI implements Disposable { myJBTable.getExpandableItemsHandler().setEnabled(false); myJBTable.setShowGrid(false); myJBTable.setModel(myTableModel); + myJBTable.setBorder(null); myJBTable.addMouseListener(new PopupHandler() { @Override public void invokePopup(Component comp, int x, int y) { - createContextMenu().getComponent().show(comp,x,y); + createContextMenu().getComponent().show(comp, x, y); } }); @@ -624,22 +635,51 @@ public class GitLogUI implements Disposable { @Override public void run() { updateByScroll(); - // todo uncomment for git tree - /*if (myGraphGutter.getComponent().isVisible()) { - myGraphGutter.getComponent().repaint(); - }*/ } - }); + }, new Runnable() { + @Override + public void run() { + if (myGraphGutter.getComponent().isVisible()) { + myGraphGutter.getComponent().repaint(); + } + } + } + ); scrollPane.getViewport().addChangeListener(myMyChangeListener); final JPanel wrapper = new DataProviderPanel(new BorderLayout()); wrapper.add(actionToolbar.getComponent(), BorderLayout.NORTH); final JPanel mainBorderWrapper = new JPanel(new BorderLayout()); - myGraphGutter.getComponent().setVisible(false); - // todo uncomment for git tree - //mainBorderWrapper.add(myGraphGutter.getComponent(), BorderLayout.WEST); + final JPanel wrapperGutter = new JPanel(new BorderLayout()); + //myGraphGutter.getComponent().setVisible(false); + myEqualToHeadr = new JPanel() { + @Override + public Dimension getPreferredSize() { + return getMySize(); + } + + @Override + public Dimension getMaximumSize() { + return getMySize(); + } + + @Override + public Dimension getMinimumSize() { + return getMySize(); + } + + public Dimension getMySize() { + final int height = myJBTable.getTableHeader().getHeight(); + final int width = myGraphGutter.getComponent().getPreferredSize().width; + return new Dimension(width, height); + } + }; + myEqualToHeadr.setBorder(BorderFactory.createMatteBorder(0,0,1,0,UIUtil.getBorderColor())); + wrapperGutter.add(myEqualToHeadr, BorderLayout.NORTH); + wrapperGutter.add(myGraphGutter.getComponent(), BorderLayout.CENTER); + mainBorderWrapper.add(wrapperGutter, BorderLayout.WEST); mainBorderWrapper.add(scrollPane, BorderLayout.CENTER); - mainBorderWrapper.setBorder(BorderFactory.createLineBorder(UIUtil.getBorderColor())); + //mainBorderWrapper.setBorder(BorderFactory.createLineBorder(UIUtil.getBorderColor())); wrapper.add(mainBorderWrapper, BorderLayout.CENTER); myDetailsPanel = new GitLogDetailsPanel(myProject, myDetailsCache, new Convertor() { @Override @@ -675,6 +715,7 @@ public class GitLogUI implements Disposable { group.add(myStructureFilterAction.asTextAction()); group.add(myCherryPickAction); group.add(ActionManager.getInstance().getAction("ChangesView.CreatePatchFromChanges")); + group.add(myMyShowTreeAction); group.add(myRefreshAction); return ActionManager.getInstance().createActionPopupMenu(GIT_LOG_TABLE_PLACE, group); } @@ -714,6 +755,7 @@ public class GitLogUI implements Disposable { myRefreshAction = new MyRefreshAction(); myRootsAction = new MyRootsAction(rootsGetter, myJBTable); group.add(myRootsAction); + group.add(myMyShowTreeAction); group.add(myRefreshAction); // todo debug //group.add(new TestIndexAction()); @@ -1268,9 +1310,13 @@ public class GitLogUI implements Disposable { myCommentSearchContext.clear(); myUsersSearchContext.clear(); - myGraphGutter.getComponent().setVisible(false); + myThereAreFilters = true; + myEqualToHeadr.getParent().setVisible(false); if (commentFilterEmpty && (myUserFilterI.myFilter == null) && myStructureFilter.myAllSelected) { - myGraphGutter.getComponent().setVisible(true); + myThereAreFilters = false; + if (myMyShowTreeAction.isSelected(null)) { + myEqualToHeadr.getParent().setVisible(true); + } myUsersSearchContext.clear(); myMediator.reload(new RootsHolder(myRootsUnderVcs), startingPoints, new GitLogFilters()); } else { @@ -1577,4 +1623,59 @@ public class GitLogUI implements Disposable { } } } + + public static class MouseOpenJBTable extends JBTable { + public MouseOpenJBTable() { + } + + public MouseOpenJBTable(TableModel model) { + super(model); + } + + @Override + public void processMouseEvent(MouseEvent e) { + super.processMouseEvent(e); + } + + @Override + public void processMouseMotionEvent(MouseEvent e) { + super.processMouseMotionEvent(e); + } + + @Override + protected void processMouseWheelEvent(MouseWheelEvent e) { + super.processMouseWheelEvent(e); + } + } + + public class MyShowTreeAction extends ToggleAction implements DumbAware { + private boolean myIsSelected; + private final GitLogSettings myInstance; + + public MyShowTreeAction() { + super("Show graph", "Show graph", IconLoader.getIcon("/icons/branch.png")); + myInstance = GitLogSettings.getInstance(myProject); + myIsSelected = myInstance.isShowTree(); + } + + @Override + public void update(AnActionEvent e) { + super.update(e); + e.getPresentation().setEnabled(! myThereAreFilters); + } + + @Override + public boolean isSelected(AnActionEvent e) { + return myIsSelected; + } + + @Override + public void setSelected(AnActionEvent e, boolean state) { + myIsSelected = state; + myInstance.setShowTree(state); + if (! myThereAreFilters) { + myEqualToHeadr.getParent().setVisible(state); + } + } + } } diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/GitTableScrollChangeListener.java b/plugins/git4idea/src/git4idea/history/wholeTree/GitTableScrollChangeListener.java index e406eb21fdef..c23565e675cf 100644 --- a/plugins/git4idea/src/git4idea/history/wholeTree/GitTableScrollChangeListener.java +++ b/plugins/git4idea/src/git4idea/history/wholeTree/GitTableScrollChangeListener.java @@ -39,7 +39,7 @@ public class GitTableScrollChangeListener implements ChangeListener { public GitTableScrollChangeListener(final JBTable table, final DetailsCache detailsCache, final BigTableTableModel tableModel, - Runnable checkSelection) { + Runnable checkSelection, final Runnable fastListener) { myDetailsCache = detailsCache; myTableModel = tableModel; myCheckSelection = checkSelection; @@ -48,6 +48,9 @@ public class GitTableScrollChangeListener implements ChangeListener { myTimer = UIUtil.createNamedTimer("Git table scroll timer",100, new ActionListener() { @Override public void actionPerformed(ActionEvent e) { + if (fastListener != null) { + fastListener.run(); + } final boolean shouldPing = (System.currentTimeMillis() - myRefreshMark) > 300; //final boolean shouldPing = false; if (((mySpeedometer.getSpeed() < 0.1) && mySpeedometer.hasData()) || shouldPing) { diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/GraphGutter.java b/plugins/git4idea/src/git4idea/history/wholeTree/GraphGutter.java index 54bb60c54c4e..768723b2aac2 100644 --- a/plugins/git4idea/src/git4idea/history/wholeTree/GraphGutter.java +++ b/plugins/git4idea/src/git4idea/history/wholeTree/GraphGutter.java @@ -15,8 +15,10 @@ */ package git4idea.history.wholeTree; -import com.intellij.ui.table.JBTable; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; @@ -30,16 +32,19 @@ import java.util.List; * Time: 4:11 PM */ public class GraphGutter { - private static final int ourLineWidth = 10; + private static final int ourLineWidth = 8; private static final int ourInterLineWidth = 1; - + private static final int ourInterRepoLineWidth = 5; + public static final int ourIndent = 2; + private int myHeaderHeight; private int myRowHeight; private final BigTableTableModel myModel; private MyComponent myComponent; private JViewport myTableViewPort; - private JBTable myJBTable; + private GitLogUI.MouseOpenJBTable myJBTable; private boolean myStarted; + private static final Logger LOG = Logger.getInstance("#git4idea.history.wholeTree.GraphGutter"); public GraphGutter(BigTableTableModel model) { myModel = model; @@ -47,7 +52,8 @@ public class GraphGutter { } public void setHeaderHeight(int headerHeight) { - myHeaderHeight = headerHeight; + //myHeaderHeight = headerHeight; + myHeaderHeight = 0; } public void setRowHeight(int rowHeight) { @@ -62,7 +68,7 @@ public class GraphGutter { myTableViewPort = tableViewPort; } - public void setJBTable(JBTable JBTable) { + public void setJBTable(GitLogUI.MouseOpenJBTable JBTable) { myJBTable = JBTable; } @@ -72,104 +78,256 @@ public class GraphGutter { // will lay near but not inside scroll pane class MyComponent extends JPanel { + MyComponent() { + setDoubleBuffered(true); + setBackground(UIUtil.getTableBackground()); + } + @Override public Dimension getPreferredSize() { Dimension preferredSize = super.getPreferredSize(); if (! myStarted) return preferredSize; int totalWires = myModel.getTotalWires(); - return new Dimension(endPoint(totalWires), preferredSize.height); + List wiresGroups = getWiresGroups(); + return new Dimension(endPoint(totalWires, wiresGroups), preferredSize.height); } - private int endPoint(final int wireNumber) { - return (wireNumber + 1) * ourLineWidth + wireNumber * ourInterLineWidth; + private int endPoint(final int wireNumber, List wiresGroups) { + int add = 0; + for (Integer wiresGroup : wiresGroups) { + if (wiresGroup - 1 <= wireNumber) { + add += ourInterRepoLineWidth - ourInterLineWidth; + } + } + return (wireNumber + 1) * ourLineWidth + wireNumber * ourInterLineWidth + add + ourIndent; + } + + private void drawConnectorsFragment(final Graphics g, + final int idxFrom, + final int yOffset, + final Set wires, + @Nullable final WireEvent event, + HashSet selected, final List wiresGroups) { + final Color darker2 = UIUtil.getTableSelectionBackground().darker(); + final Color darker = new Color(255,128,0); + + int rowYOffset = yOffset; + for (int i = idxFrom; i < event.getCommitIdx(); i++) { + for (Integer wire : wires) { + g.setColor(selected.contains(i) ? UIUtil.getTableSelectionForeground() : (wire/2 % 2 == 1 ? darker : darker2)); + int startXPoint = startPoint(wire, wiresGroups) + ourLineWidth/2; + g.drawLine(startXPoint, rowYOffset, startXPoint, rowYOffset + myRowHeight); + } + rowYOffset += myRowHeight; + } + + final int eventWire = myModel.getCorrectedWire(myModel.getCommitAt(event.getCommitIdx())); + g.setColor(selected.contains(event.getCommitIdx()) ? UIUtil.getTableSelectionForeground() : (eventWire/2 % 2 == 1 ? darker : darker2)); + int eventStartXPoint = startPoint(eventWire, wiresGroups) + ourLineWidth/2; + + final Set skip = new HashSet(); + if (event.isStart() && event.isEnd()) { + return; + } + if (event.isStart()) { + skip.add(eventWire); + g.drawLine(eventStartXPoint, rowYOffset + myRowHeight/2, eventStartXPoint, rowYOffset + myRowHeight); + } + if (event.isEnd()) { + skip.add(eventWire); + g.drawLine(eventStartXPoint, rowYOffset, eventStartXPoint, rowYOffset + myRowHeight/2); + } + + + final int[] commitsStarts = event.getCommitsStarts(); + if (commitsStarts != null) { + for (int commitsStart : commitsStarts) { + if (commitsStart == -1) continue; + final int wire = myModel.getCorrectedWire(myModel.getCommitAt(commitsStart)); + int startXPoint = startPoint(wire, wiresGroups) + ourLineWidth/2; + g.setColor(selected.contains(event.getCommitIdx()) ? UIUtil.getTableSelectionForeground() : (wire/2 % 2 == 1 ? darker : darker2)); + g.drawLine(startXPoint, rowYOffset + myRowHeight, eventStartXPoint, rowYOffset + myRowHeight/2); + } + } + final int[] wireEnds = event.getWireEnds(); + if (wireEnds != null) { + for (int end : wireEnds) { + if (end == -1) continue; + final int wire = myModel.getCorrectedWire(myModel.getCommitAt(end)); + skip.add(wire); + int startXPoint = startPoint(wire, wiresGroups) + ourLineWidth/2; + g.setColor(selected.contains(event.getCommitIdx()) ? UIUtil.getTableSelectionForeground() : (wire/2 % 2 == 1 ? darker : darker2)); + g.drawLine(startXPoint, rowYOffset, eventStartXPoint, rowYOffset + myRowHeight/2); + } + } + + for (Integer wire : wires) { + if (skip.contains(wire)) continue; + int startXPoint = startPoint(wire, wiresGroups) + ourLineWidth/2; + g.setColor(selected.contains(event.getCommitIdx()) ? UIUtil.getTableSelectionForeground() : (wire/2 % 2 == 1 ? darker : darker2)); + g.drawLine(startXPoint, rowYOffset, startXPoint, rowYOffset + myRowHeight); + } } @Override protected void paintComponent(Graphics g) { - super.paintComponent(g); - if (! myStarted) return; - - Graphics graphics = g.create(); try { - int height = getHeight(); + super.paintComponent(g); + if (! myStarted) return; - // separators - List wiresGroups = myModel.getWiresGroups(); - int running = 0; - if (wiresGroups.size() > 1) { - for (int i = 0; i < wiresGroups.size() - 1; i++) { - Integer integer = wiresGroups.get(i); - int x1 = (running + integer) == 0 ? 0 : endPoint(running + integer - 1); - graphics.setColor(UIUtil.getBorderColor()); - graphics.drawLine(x1, 0, x1, height); - running += integer; - } - } - // cells - int yOffset = (int) myTableViewPort.getViewPosition().getY(); - int integerPart = yOffset / myRowHeight; - int firstCellPart = yOffset - integerPart * myRowHeight; - - if (firstCellPart > 0) { - drawFirst(graphics, firstCellPart, integerPart); - } - int width = getWidth(); - int upBound = firstCellPart == 0 ? 0 : myRowHeight - firstCellPart; // todo +- - upBound += myHeaderHeight; - int idx = integerPart + (firstCellPart > 0 ? 1 : 0); - - BigTableTableModel.WiresGroupIterator groupIterator; + Graphics graphics = g.create(); try { - groupIterator = myModel.getGroupIterator(idx); - } catch (Exception e) { - return; - // - // TODO remove!!! - } - List firstUsed = groupIterator.getFirstUsed(); - while (upBound < height && idx < myModel.getRowCount()) { - CommitI commitAt = myModel.getCommitAt(idx); + int height = getHeight(); - WireEvent eventForRow = groupIterator.getEventForRow(idx); - if (eventForRow != null) { - //todo minus, plus.. + // cells + int yOffset = (int) myTableViewPort.getViewPosition().getY(); + int integerPart = yOffset / myRowHeight; + int firstCellPart = yOffset - integerPart * myRowHeight; + int width = getWidth(); + int upBound = firstCellPart == 0 ? 0 : - firstCellPart; + upBound += myHeaderHeight; + //int idx = integerPart + (firstCellPart > 0 ? 1 : 0); + int idx = integerPart; + int lastIdx = idx + (getHeight()/myRowHeight + 1); + + final int[] selectedRows = myJBTable.getSelectedRows(); + drawSelection(graphics, width, upBound, idx, lastIdx, selectedRows); + final HashSet selected = new HashSet(); + for (int selectedRow : selectedRows) { + selected.add(selectedRow); } - // todo temp out - /*for (Integer i : firstUsed) { - int start = startPoint(i); - graphics.setColor(UIUtil.getBorderColor()); - graphics.drawRect(start - 1, upBound, start + 1, upBound + myRowHeight); - }*/ - if (! commitAt.holdsDecoration()) { - int correctedWire = myModel.getCorrectedWire(commitAt); - int startXPoint = startPoint(correctedWire); - graphics.setColor(Color.black); - ((Graphics2D) graphics).drawArc(startXPoint + ourLineWidth / 2 - 4, upBound + myRowHeight / 2 - 4, 8, 8, 0, 360); - graphics.setColor(correctedWire == 0 ? Color.red : Color.yellow); - ((Graphics2D) graphics).fillArc(startXPoint + ourLineWidth / 2 - 4, upBound + myRowHeight / 2 - 4, 8, 8, 0, 360); - //graphics.drawString("" + correctedWire, startXPoint, upBound + myRowHeight); - } else { - graphics.setColor(Color.black); - //((Graphics2D) graphics).drawArc(startXPoint + ourLineWidth/2 - 2, upBound + myRowHeight/2 - 2, 4, 4, 0, 360); - //graphics.drawString("H", 0, upBound + myRowHeight); - } - graphics.setColor(UIUtil.getBorderColor()); - graphics.drawLine(0, upBound, width, upBound); - ++ idx; - upBound += myRowHeight; + // separators + List wiresGroups = getWiresGroups(); + + drawRepoBounds(graphics, height, wiresGroups); + + drawConnectors(graphics, lastIdx, upBound, idx, selected, wiresGroups); + + drawPoints(graphics, lastIdx, upBound, idx, selected, wiresGroups); + } finally { + graphics.dispose(); } - } finally { - graphics.dispose(); + } catch (Exception e) { + LOG.info(e); } } - private int startPoint(int correctedWire) { - return correctedWire == 0 ? 0 : endPoint(correctedWire - 1); + private List getWiresGroups() { + List wiresGroups = myModel.getWiresGroups(); + if (wiresGroups == null) return Collections.emptyList(); + int running = 0; + for (int i = 0; i < wiresGroups.size(); i++) { + Integer integer = wiresGroups.get(i); + wiresGroups.set(i, running + integer); + running += integer; + } + return wiresGroups; } - private void drawFirst(Graphics graphics, int firstCellPart, int idx) { - //To change body of created methods use File | Settings | File Templates. + private void drawSelection(Graphics graphics, int width, int upBound, int idx, int lastIdx, int[] selectedRows) { + for (int selectedRow : selectedRows) { + if (selectedRow >= idx && selectedRow <= lastIdx) { + graphics.setColor(UIUtil.getTableSelectionBackground()); + graphics.fillRect(0, upBound + (selectedRow - idx) * myRowHeight,width,myRowHeight); + } + } + } + + private void drawPoints(Graphics graphics, int lastIdx, int upBound, int idx, HashSet selected, List wiresGroups) { + final Color darker = UIUtil.getTableSelectionBackground(); + final Color fill = new Color(176,230,255); + while (idx <= lastIdx && idx < myModel.getRowCount()) { + CommitI commitAt = myModel.getCommitAt(idx); + final boolean contains = selected.contains(idx); + + if (! commitAt.holdsDecoration()) { + int correctedWire = myModel.getCorrectedWire(commitAt); + int startXPoint = startPoint(correctedWire, wiresGroups); + graphics.setColor(contains ? UIUtil.getTableSelectionForeground() : fill); + ((Graphics2D) graphics).fillArc(startXPoint + ourLineWidth / 2 - 4, upBound + myRowHeight / 2 - 4, 7, 7, 0, 360); + //graphics.setColor(contains ? UIUtil.getTableSelectionForeground() : UIUtil.getTableSelectionBackground().brighter()); + + graphics.setColor(contains ? UIUtil.getTableSelectionForeground() : UIUtil.getTableSelectionBackground()); + ((Graphics2D) graphics).drawArc(startXPoint + ourLineWidth / 2 - 4 + 1, upBound + myRowHeight / 2 - 4, 7, 7, 0, 360); + + if (! contains) { + graphics.setColor(UIUtil.getTableSelectionForeground()); + ((Graphics2D) graphics).drawArc(startXPoint + ourLineWidth / 2 - 4 + 1, upBound + myRowHeight / 2 - 4 + 1, 6, 6, 100, 90); + } + + graphics.setColor(contains ? UIUtil.getTableSelectionForeground() : darker); + ((Graphics2D) graphics).drawArc(startXPoint + ourLineWidth / 2 - 4, upBound + myRowHeight / 2 - 4, 7, 7, 0, 360); + //graphics.drawString("" + correctedWire, startXPoint, upBound + myRowHeight); + } else { + //((Graphics2D) graphics).drawArc(startXPoint + ourLineWidth/2 - 2, upBound + myRowHeight/2 - 2, 4, 4, 0, 360); + //graphics.drawString("H", 0, upBound + myRowHeight); + } + ++ idx; + upBound += myRowHeight; + } + } + + private void drawConnectors(Graphics graphics, int lastIdx, int upBound, int idx, HashSet selected, List wiresGroups) { + final Map groupIterators = myModel.getGroupIterators(idx); + for (Map.Entry entry : groupIterators.entrySet()) { + final WireEventsIterator eventsIterator = entry.getValue(); + int idxFrom = idx; + int yOff = upBound; + Set used = new HashSet(eventsIterator.getFirstUsed()); + final Iterator iterator = eventsIterator.getWireEventsIterator(); + while (iterator.hasNext()) { + final WireEvent wireEvent = iterator.next(); + + if (wireEvent.getCommitIdx() >= idx) { + drawConnectorsFragment(graphics, idxFrom, yOff, used, wireEvent, selected, wiresGroups); + int delta = wireEvent.getCommitIdx() + 1 - idxFrom; + delta = delta < 0 ? 0 : delta; + yOff += delta * myRowHeight; + idxFrom = wireEvent.getCommitIdx() + 1; + } + // add starts, minus ended + final int[] wireEnds = wireEvent.getWireEnds(); + if (wireEnds != null) { + for (int wireEnd : wireEnds) { + used.remove(Integer.valueOf(myModel.getCorrectedWire(myModel.getCommitAt(wireEnd)))); + } + } + if (wireEvent.isEnd()) { + used.remove(Integer.valueOf(myModel.getCorrectedWire(myModel.getCommitAt(wireEvent.getCommitIdx())))); + } + final int[] commitsStarts = wireEvent.getCommitsStarts(); + if (commitsStarts != null) { + for (int commitsStart : commitsStarts) { + if (commitsStart == -1) continue; + used.add(myModel.getCorrectedWire(myModel.getCommitAt(commitsStart))); + } + } + if (wireEvent.isStart()) { + used.add(myModel.getCorrectedWire(myModel.getCommitAt(wireEvent.getCommitIdx()))); + } + + if (wireEvent.getCommitIdx() > lastIdx) break; + } + } + } + + private void drawRepoBounds(Graphics graphics, int height, List wiresGroups) { + graphics.setColor(UIUtil.getBorderColor()); + if (wiresGroups.size() > 1) { + for (int i = 0; i < wiresGroups.size() - 1; i++) { + Integer integer = wiresGroups.get(i); + int x1 = integer == 0 ? 0 : endPoint(integer - 1, wiresGroups); + if (x1 > 0) { + x1 -= ourInterRepoLineWidth/2 + 1; + } + graphics.drawLine(x1, 0, x1, height); + } + } + } + + private int startPoint(int correctedWire, List wiresGroups) { + return correctedWire == 0 ? ourIndent : endPoint(correctedWire - 1, wiresGroups); } } } diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/SkeletonBuilder.java b/plugins/git4idea/src/git4idea/history/wholeTree/SkeletonBuilder.java index 03fa509b7f40..6dd5228e4eea 100644 --- a/plugins/git4idea/src/git4idea/history/wholeTree/SkeletonBuilder.java +++ b/plugins/git4idea/src/git4idea/history/wholeTree/SkeletonBuilder.java @@ -37,6 +37,7 @@ public class SkeletonBuilder { // !! can intersect with existing, but own lifecycle private final BidirectionalMap myFutureSeizedWires; private final Convertor> myFutureConvertor; + private int myMaxWireNum; public SkeletonBuilder(WireEventsListener treeNavigation) { mySkeleton = treeNavigation; @@ -53,6 +54,7 @@ public class SkeletonBuilder { return getFutureWireStarts(o); } }; + myMaxWireNum = 0; } public void consume(final CommitI commitI, final List parents, final ReadonlyList commits, final int rowCount) { @@ -84,7 +86,6 @@ public class SkeletonBuilder { } final CommitI waitingI = commits.get(waiting.myIdx); - //waiting.parentFound(); if (waiting.isMerge()) { // put/update start event - now we know index so can create/update wire event @@ -96,18 +97,6 @@ public class SkeletonBuilder { if (seized != null && seized == waiting.myIdx && waitingI.getWireNumber() != wireNumber && something == null) { // return willReturnTheirWires.add(waiting); - /*if (wireNumber == -1) { // if this commit still doesn't have wire - // there is no other commits on the wire after parent -> use it - wireNumber = waitingI.getWireNumber(); - } - else { - // if there are no other children of that commit. wire dies - if (waiting.allParentsFound()) { - myRing.back(waitingI.getWireNumber()); - // end of waiting commits' wire - mySkeleton.parentWireEnds(rowCount, waiting.myIdx); - } - }*/ } } @@ -121,7 +110,6 @@ public class SkeletonBuilder { // event about branch! if (awaitingList.size() > 1) { // merge event - //mySkeleton.parentWireEnds(); // fix? final int[] ends = new int[awaitingList.size()]; for (int i = 0; i < awaitingList.size(); i++) { final WaitingItem waiting = awaitingList.get(i); @@ -138,12 +126,10 @@ public class SkeletonBuilder { } // register what we choose - if (mySeizedWires.containsValue(rowCount) && mySeizedWires.getKeysByValue(rowCount).iterator().next() != wireNumber) { - System.out.println("caught on adding!"); - } mySeizedWires.put(wireNumber, rowCount); commitI.setWireNumber(wireNumber); + myMaxWireNum = Math.max(myMaxWireNum, myRing.getMaxNumber()); if (parents.isEmpty()) { // end event mySkeleton.wireEnds(rowCount); @@ -178,64 +164,11 @@ public class SkeletonBuilder { } } } - - public void oldBecameNew(final Map map) { - final MultiMap backCopy = new MultiMap(); - backCopy.putAllValues(myBackIndex); - myBackIndex.clear(); - for (Map.Entry> entry : backCopy.entrySet()) { - Integer key = map.get(entry.getKey()); - Collection items = entry.getValue(); - if (key == null) { - key = entry.getKey(); - } - else { - for (WaitingItem item : items) { - item.myIdx = key; - } - } - myBackIndex.put(key, items); - } - - // seized - BidirectionalMap copy = new BidirectionalMap(); - copy.putAll(mySeizedWires); - mySeizedWires.clear(); - - for (Integer oldIdx : copy.values()) { - List wires = copy.getKeysByValue(oldIdx); - if (wires == null || wires.size() != 1) { - System.out.println("www"); - } - assert (wires != null && wires.size() == 1); - Integer newIdx = map.get(oldIdx); - newIdx = newIdx == null ? oldIdx : newIdx; - mySeizedWires.put(wires.get(0), newIdx); - } + public int getMaxWireNum() { + return myMaxWireNum; } - /*public void oldBecameNew(int was, int is) { - Collection removed = myBackIndex.remove(was); - if (removed != null) { - for (WaitingItem commit : removed) { - commit.myIdx = is; - } - myBackIndex.put(is, removed); - } - - List keysByValue = mySeizedWires.getKeysByValue(was); - if (keysByValue != null && ! keysByValue.isEmpty()) { - if (keysByValue.size() > 1) { - System.out.println("Ooops!"); - } - assert keysByValue.size() == 1; // each commit only on one wire - mySeizedWires.remove(was); - int value = keysByValue.get(0); - mySeizedWires.put(value, is); - } - }*/ - // just some order private static class CommitsComparator implements Comparator { private final static CommitsComparator ourInstance = new CommitsComparator(); @@ -274,39 +207,6 @@ public class SkeletonBuilder { } } - private static class WaitingCommit { - private int myIdx; // id of commit that's wait for parents (self) - private int myNumParents; // i.e. a start - private final boolean myIsMerge; - private int myWire; - - private WaitingCommit(int idx, int numParents) { - myIdx = idx; - myNumParents = numParents; - myIsMerge = myNumParents > 1; - } - - public boolean isMerge() { - return myIsMerge; - } - - public void parentFound() { - -- myNumParents; - } - - public boolean allParentsFound() { - return myNumParents == 0; - } - - public int getWire() { - return myWire; - } - - public void setWire(int wire) { - myWire = wire; - } - } - public List getFutureWireStarts(final int idx) { Collection waitingItems = myBackIndex.get(idx); if (waitingItems == null || waitingItems.isEmpty()) return Collections.emptyList(); diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/TreeNavigationImpl.java b/plugins/git4idea/src/git4idea/history/wholeTree/TreeNavigationImpl.java index e252b0e222c2..28e28f28b578 100644 --- a/plugins/git4idea/src/git4idea/history/wholeTree/TreeNavigationImpl.java +++ b/plugins/git4idea/src/git4idea/history/wholeTree/TreeNavigationImpl.java @@ -12,10 +12,8 @@ */ package git4idea.history.wholeTree; -import com.intellij.openapi.util.Ref; import com.intellij.openapi.vcs.Ring; import com.intellij.util.Consumer; -import com.intellij.util.containers.BidirectionalMap; import com.intellij.util.containers.Convertor; import com.intellij.util.containers.ReadonlyList; @@ -33,15 +31,12 @@ public class TreeNavigationImpl implements TreeNavigation, WireEventsListener { private final int myCommitIndexInterval; // maximum number of wire events, after which index entry should be written private final int myNumWiresInGroup; - - private int myMaximumWires; public TreeNavigationImpl(final int commitIndexInterval, final int numWiresInGroup) { myCommitIndexInterval = commitIndexInterval; myNumWiresInGroup = numWiresInGroup; myWireEvents = new TreeMap(); myRingIndex = new TreeMap(); - myMaximumWires = 0; } public void recalcIndex(final ReadonlyList commits, final Convertor> future) { @@ -85,19 +80,12 @@ public class TreeNavigationImpl implements TreeNavigation, WireEventsListener { } } - // todo write start immediately in event in form of hash - // todo to be able to get it here, when building index - // todo: problem: when doing index, we don't have "future commits" wires! private void performOnRing(final Ring ring, final WireEvent event, final ReadonlyList convertor, List futureWireStarts) { final int[] wireEnds = event.getWireEnds(); if (wireEnds != null) { for (int wireEnd : wireEnds) { int wireNumber = convertor.get(wireEnd).getWireNumber(); - if (ring.haveInFree(wireNumber)) { - System.out.println("assertion will rise here, commits size: " + convertor.getSize() + " event idx: " + event.getCommitIdx()); - } - //System.out.println("back(1): " + wireNumber + " from: " + event.getCommitIdx()); ring.back(wireNumber); } } @@ -121,7 +109,6 @@ public class TreeNavigationImpl implements TreeNavigation, WireEventsListener { for (Integer wireStart : futureWireStarts) { ring.minus(wireStart); } - myMaximumWires = Math.max(myMaximumWires, ring.getMaxNumber()); } public Collection getTail(int rowInclusive) { @@ -149,15 +136,16 @@ public class TreeNavigationImpl implements TreeNavigation, WireEventsListener { if (entry.getKey() == row) { return ring; } - System.out.println("-----------------> row = " + row); + //System.out.println("-----------------> row = " + row); final Iterator iterator = createWireEventsIterator(entry.getKey()); while (iterator.hasNext()) { final WireEvent event = iterator.next(); if (event.getCommitIdx() >= row) { return ring; } - System.out.println("event: " + event.toString()); - System.out.println("ring before: " + ring.toString()); +// System.out.println("event: " + event.toString()); +// System.out.println("ring before: " + ring.toString()); + //if (event.getCommitIdx() == entry.getKey()) continue; performOnRing(ring, event, commits, future.convert(event.getCommitIdx())); } return ring; @@ -306,10 +294,6 @@ public class TreeNavigationImpl implements TreeNavigation, WireEventsListener { myMaximumWires = Math.max(myMaximumWires, ring.getMaxNumber()); }*/ - public int getMaximumWires() { - return myMaximumWires; - } - private void recountFragmentZwichem(ReadonlyList commits, Map recalculateMap, int runningCommitNumber, diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/WireEvent.java b/plugins/git4idea/src/git4idea/history/wholeTree/WireEvent.java index c90457510f25..9d2d51b63cde 100644 --- a/plugins/git4idea/src/git4idea/history/wholeTree/WireEvent.java +++ b/plugins/git4idea/src/git4idea/history/wholeTree/WireEvent.java @@ -53,6 +53,14 @@ public class WireEvent { } } + public void setWireEnds(@Nullable int[] wireEnds) { + myWireEnds = wireEnds; + } + + public void setCommitsStarts(int[] commitsStarts) { + myCommitsStarts = commitsStarts; + } + @Nullable public int[] getWireEnds() { return myWireEnds; diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/WireEventsIterator.java b/plugins/git4idea/src/git4idea/history/wholeTree/WireEventsIterator.java new file mode 100644 index 000000000000..c83b0ae04cf2 --- /dev/null +++ b/plugins/git4idea/src/git4idea/history/wholeTree/WireEventsIterator.java @@ -0,0 +1,34 @@ +/* + * Copyright 2000-2011 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package git4idea.history.wholeTree; + +import java.util.Iterator; +import java.util.List; + +/** + * Created by IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 10/20/11 + * Time: 11:49 AM + * To change this template use File | Settings | File Templates. + */ +public interface WireEventsIterator { + Integer getFloor(); + + Iterator getWireEventsIterator(); + + List getFirstUsed(); +} diff --git a/plugins/git4idea/tests/git4idea/tests/GroupingMergerTest.java b/plugins/git4idea/tests/git4idea/tests/GroupingMergerTest.java new file mode 100644 index 000000000000..edf771450450 --- /dev/null +++ b/plugins/git4idea/tests/git4idea/tests/GroupingMergerTest.java @@ -0,0 +1,102 @@ +/* + * Copyright 2000-2011 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package git4idea.tests; + +import com.intellij.openapi.vcs.BigArray; +import com.intellij.openapi.vcs.ComparableComparator; +import com.intellij.openapi.vcs.GroupingMerger; +import junit.framework.Assert; +import junit.framework.TestCase; + +import java.util.HashMap; +import java.util.Map; + +/** + * Created by IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 10/19/11 + * Time: 3:16 PM + */ +public class GroupingMergerTest extends TestCase { + public void testSimple() throws Exception { + final Map recalculation = new HashMap(); + // 2nd - index + final Map newInsertions = new HashMap(); + + final GroupingMerger merger = new GroupingMerger() { + @Override + protected String getGroup(Integer integer) { + return ""; + } + + @Override + protected Integer wrapGroup(String s, Integer item) { + return -1; + } + + @Override + protected void oldBecame(int was, int is) { + recalculation.put(was, is); + } + + @Override + protected void afterConsumed(Integer integer, int i) { + newInsertions.put(integer, i); + } + }; + + final BigArray main = new BigArray(4); + main.add(10); + main.add(20); + main.add(30); + main.add(40); + main.add(50); + main.add(60); + main.add(70); + main.add(80); + main.add(90); + main.add(100); + main.add(110); + + final BigArray insert = new BigArray(4); + insert.add(11); + insert.add(21); + insert.add(31); + insert.add(41); + insert.add(51); + insert.add(61); + insert.add(71); + insert.add(81); + insert.add(91); + insert.add(101); + insert.add(111); + + merger.firstPlusSecond(main, insert, new ComparableComparator(), -1); + + int added = 1; + // 20-110 + for (int i = 1; i <= 10; i++) { + final Integer integer = recalculation.get(i); + Assert.assertEquals(i + added, (int)integer); + ++ added; + } + int startIdx = 1; + for (int i = 11; i <= 111; i+=10) { + Assert.assertEquals(startIdx, (int)newInsertions.get(i)); + startIdx += 2; + } + } +} From cf1436f0b7033c973a82dc8834c29987b40dce1e Mon Sep 17 00:00:00 2001 From: peter Date: Thu, 20 Oct 2011 19:09:18 +0200 Subject: [PATCH 14/19] gdsl: assertVersion as agreed --- .../groovy/dsl/GroovyDslExecutor.groovy | 8 +++++-- .../plugins/groovy/dsl/DsldTest.groovy | 24 +++++++++++++++---- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/dsl/GroovyDslExecutor.groovy b/plugins/groovy/src/org/jetbrains/plugins/groovy/dsl/GroovyDslExecutor.groovy index 3cf1d94cfaf1..3290efc2b831 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/dsl/GroovyDslExecutor.groovy +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/dsl/GroovyDslExecutor.groovy @@ -105,8 +105,12 @@ public class GroovyDslExecutor { return StringUtil.compareVersionNumbers(ideaVersion, ver) >= 0 } else if (ver instanceof Map) { - def we = ver.ide?.intellij - return we != null && StringUtil.compareVersionNumbers(ideaVersion, we) >= 0 + if (ver.dsl) { + return StringUtil.compareVersionNumbers('1.0', ver.dsl) >= 0 + } + if (ver.intellij) { + return StringUtil.compareVersionNumbers(ideaVersion, ver.intellij) >= 0 + } } return false } diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/dsl/DsldTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/dsl/DsldTest.groovy index 026426453834..d2c534dad083 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/dsl/DsldTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/dsl/DsldTest.groovy @@ -127,13 +127,13 @@ def doo() { println foo } public void testSupportsVersion() { checkHighlighting ''' -if (supportsVersion(ide:[intellij:'9.0'])) { +if (supportsVersion(intellij:'9.0')) { contribute(currentType("java.lang.String")) { property name:"foo" } } else { contribute(currentType("java.lang.String")) { property name:"bar" } } -if (!supportsVersion(ide:[groovyEclipse:'9.0'])) { +if (!supportsVersion(groovyEclipse:'9.0')) { contribute(currentType("java.lang.String")) { property name:"goo" } } ''', @@ -142,7 +142,15 @@ if (!supportsVersion(ide:[groovyEclipse:'9.0'])) { public void testAssertVersion() { checkHighlighting ''' -assertVersion ide:[intellij:'9.0'] +assertVersion dsl:'1.0' +contribute(currentType("java.lang.String")) { property name:"foo" } +''', + 'println "".foo' + } + + public void testAssertVersionDsl() { + checkHighlighting ''' +assertVersion intellij:'9.0' contribute(currentType("java.lang.String")) { property name:"foo" } ''', 'println "".foo' @@ -150,7 +158,15 @@ contribute(currentType("java.lang.String")) { property name:"foo" } public void testAssertVersionFail() { checkHighlighting ''' -assertVersion ide:[intellij:'239.0'] +assertVersion intellij:'239.0' +contribute(currentType("java.lang.String")) { property name:"foo" } +''', + 'println "".foo' + } + + public void testAssertVersionFailDsl() { + checkHighlighting ''' +assertVersion dsl:'239.0' contribute(currentType("java.lang.String")) { property name:"foo" } ''', 'println "".foo' From e245c5a2c4f0f22cadc4da058243e205e07c5039 Mon Sep 17 00:00:00 2001 From: peter Date: Thu, 20 Oct 2011 20:10:31 +0200 Subject: [PATCH 15/19] after a project is opened, schedule hiding of all non-sticky balloons --- .../src/com/intellij/util/Alarm.java | 3 ++- .../impl/NotificationsManagerImpl.java | 22 ++++++++++++++----- .../src/com/intellij/ui/BalloonImpl.java | 11 +++++----- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/platform/platform-api/src/com/intellij/util/Alarm.java b/platform/platform-api/src/com/intellij/util/Alarm.java index 0c33ef8e85d7..8f0117dd4844 100644 --- a/platform/platform-api/src/com/intellij/util/Alarm.java +++ b/platform/platform-api/src/com/intellij/util/Alarm.java @@ -25,6 +25,7 @@ import com.intellij.openapi.util.Disposer; import com.intellij.util.ui.update.Activatable; import com.intellij.util.ui.update.UiNotifyConnector; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; @@ -100,7 +101,7 @@ public class Alarm implements Disposable { _addRequest(request, delay, ModalityState.stateForComponent(myActivationComponent)); } - public void addRequest(final Runnable request, int delayMillis, final ModalityState modalityState) { + public void addRequest(final Runnable request, int delayMillis, @Nullable final ModalityState modalityState) { LOG.assertTrue(myThreadToUse == ThreadToUse.SWING_THREAD); _addRequest(request, delayMillis, modalityState); } diff --git a/platform/platform-impl/src/com/intellij/notification/impl/NotificationsManagerImpl.java b/platform/platform-impl/src/com/intellij/notification/impl/NotificationsManagerImpl.java index 3221dee9f473..6af57cef00f1 100644 --- a/platform/platform-impl/src/com/intellij/notification/impl/NotificationsManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/notification/impl/NotificationsManagerImpl.java @@ -22,6 +22,7 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.ApplicationComponent; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; +import com.intellij.openapi.project.ProjectManagerAdapter; import com.intellij.openapi.startup.StartupManager; import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.ui.popup.*; @@ -30,6 +31,7 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.wm.ToolWindowManager; import com.intellij.openapi.wm.WindowManager; import com.intellij.openapi.wm.impl.IdeFrameImpl; +import com.intellij.ui.BalloonImpl; import com.intellij.ui.components.panels.NonOpaquePanel; import com.intellij.util.ArrayUtil; import com.intellij.util.PairFunction; @@ -179,9 +181,6 @@ public class NotificationsManagerImpl extends NotificationsManager implements No (toolWindowId == null || project == null || !Arrays.asList(ToolWindowManager.getInstance(project).getToolWindowIds()).contains(toolWindowId))) { type = NotificationDisplayType.BALLOON; } - if (type == NotificationDisplayType.BALLOON && ProjectManager.getInstance().getOpenProjects().length == 0) { - type = NotificationDisplayType.STICKY_BALLOON; - } switch (type) { case NONE: @@ -219,10 +218,23 @@ public class NotificationsManagerImpl extends NotificationsManager implements No Window window = findWindowForBalloon(project); if (window instanceof IdeFrameImpl) { - boolean sticky = NotificationDisplayType.STICKY_BALLOON == displayType; - Balloon balloon = createBalloon(notification, false, !sticky, !sticky); + final ProjectManager projectManager = ProjectManager.getInstance(); + boolean noProjects = projectManager.getOpenProjects().length == 0; + boolean sticky = NotificationDisplayType.STICKY_BALLOON == displayType || noProjects; + final Balloon balloon = createBalloon(notification, false, !sticky, !sticky); Disposer.register(project != null ? project : ApplicationManager.getApplication(), balloon); ((IdeFrameImpl)window).getBalloonLayout().add(balloon); + if (noProjects && NotificationDisplayType.BALLOON == displayType) { + projectManager.addProjectManagerListener(new ProjectManagerAdapter() { + @Override + public void projectOpened(Project project) { + projectManager.removeProjectManagerListener(this); + if (!balloon.isDisposed()) { + ((BalloonImpl)balloon).startFadeoutTimer(300); + } + } + }); + } } } diff --git a/platform/platform-impl/src/com/intellij/ui/BalloonImpl.java b/platform/platform-impl/src/com/intellij/ui/BalloonImpl.java index f759b855403f..a93f47537bb2 100644 --- a/platform/platform-impl/src/com/intellij/ui/BalloonImpl.java +++ b/platform/platform-impl/src/com/intellij/ui/BalloonImpl.java @@ -569,7 +569,7 @@ public class BalloonImpl implements Disposable, Balloon, LightweightWindow, Posi myFadedIn = true; - startFadeoutTimer(); + startFadeoutTimer((int)BalloonImpl.this.myFadeoutTime); } else { layeredPane.remove(myComp); @@ -592,14 +592,13 @@ public class BalloonImpl implements Disposable, Balloon, LightweightWindow, Posi myAnimator.resume(); } - private void startFadeoutTimer() { - if (myFadeoutTime > 0) { - Alarm fadeoutAlarm = new Alarm(this); - fadeoutAlarm.addRequest(new Runnable() { + public void startFadeoutTimer(final int fadeoutTime) { + if (fadeoutTime > 0) { + new Alarm(this).addRequest(new Runnable() { public void run() { hide(); } - }, (int)myFadeoutTime, null); + }, fadeoutTime, null); } } From 6079af3a74c360fed8352f40b93d8b0b4de653fd Mon Sep 17 00:00:00 2001 From: peter Date: Thu, 20 Oct 2011 20:36:00 +0200 Subject: [PATCH 16/19] NPE EA-30173 --- .../codeInsight/completion/JavaInheritorsGetter.java | 3 ++- java/java-psi-api/src/com/intellij/psi/util/PsiUtil.java | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaInheritorsGetter.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaInheritorsGetter.java index 23ccc24f9d03..88457e842c85 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaInheritorsGetter.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaInheritorsGetter.java @@ -161,7 +161,8 @@ public class JavaInheritorsGetter extends CompletionProvider -1) { final JavaResolveResult resolveResult = methodCallExpression.resolveMethodGenerics(); - return PsiUtil.getApplicabilityLevel((PsiMethod)resolveResult.getElement(), resolveResult.getSubstitutor(), types, PsiUtil.getLanguageLevel(originalPosition)) + PsiMethod method = (PsiMethod)resolveResult.getElement(); + return method == null || PsiUtil.getApplicabilityLevel(method, resolveResult.getSubstitutor(), types, PsiUtil.getLanguageLevel(originalPosition)) != MethodCandidateInfo.ApplicabilityLevel.NOT_APPLICABLE; } } diff --git a/java/java-psi-api/src/com/intellij/psi/util/PsiUtil.java b/java/java-psi-api/src/com/intellij/psi/util/PsiUtil.java index d6f1c3a40b21..742b140f1e93 100644 --- a/java/java-psi-api/src/com/intellij/psi/util/PsiUtil.java +++ b/java/java-psi-api/src/com/intellij/psi/util/PsiUtil.java @@ -437,8 +437,8 @@ public final class PsiUtil extends PsiUtilCore { return getApplicabilityLevel(method, substitutorForMethod, argList.getExpressionTypes(), getLanguageLevel(argList)); } - public static int getApplicabilityLevel(final PsiMethod method, final PsiSubstitutor substitutorForMethod, @NotNull final PsiType[] args, - final LanguageLevel languageLevel) { + public static int getApplicabilityLevel(@NotNull final PsiMethod method, @NotNull final PsiSubstitutor substitutorForMethod, @NotNull final PsiType[] args, + @NotNull final LanguageLevel languageLevel) { final PsiParameter[] parms = method.getParameterList().getParameters(); if (args.length < parms.length - 1) return ApplicabilityLevel.NOT_APPLICABLE; @@ -472,7 +472,7 @@ public final class PsiUtil extends PsiUtilCore { } private static boolean areFirstArgumentsApplicable(final PsiType[] args, final PsiParameter[] parms, final LanguageLevel languageLevel, - final PsiSubstitutor substitutorForMethod) { + @NotNull final PsiSubstitutor substitutorForMethod) { for (int i = 0; i < parms.length - 1; i++) { final PsiType type = args[i]; @@ -488,7 +488,7 @@ public final class PsiUtil extends PsiUtilCore { private static PsiType getParameterType(final PsiParameter parameter, final LanguageLevel languageLevel, - final PsiSubstitutor substitutor) { + @NotNull final PsiSubstitutor substitutor) { PsiType parmType = parameter.getType(); if (parmType instanceof PsiClassType) { parmType = ((PsiClassType)parmType).setLanguageLevel(languageLevel); From 4dbcdeb65b447406006dddac156f1c59c83aa4e0 Mon Sep 17 00:00:00 2001 From: peter Date: Fri, 21 Oct 2011 13:40:20 +0200 Subject: [PATCH 17/19] named arguments defined in file have lower priority --- .../groovy/lang/completion/MapArgumentCompletionProvider.java | 4 +++- .../plugins/groovy/completion/GroovyCompletionTest.groovy | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/MapArgumentCompletionProvider.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/MapArgumentCompletionProvider.java index e0e41be09b1a..199b3ac5a207 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/MapArgumentCompletionProvider.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/MapArgumentCompletionProvider.java @@ -116,7 +116,9 @@ class MapArgumentCompletionProvider extends CompletionProvider Date: Fri, 21 Oct 2011 13:55:43 +0200 Subject: [PATCH 18/19] skip same variable reference highlights --- .../intellij/codeInsight/template/impl/TemplateState.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateState.java b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateState.java index ee3aec8c72b8..8694e189c9ed 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateState.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateState.java @@ -24,7 +24,6 @@ import com.intellij.codeInsight.completion.OffsetMap; import com.intellij.codeInsight.lookup.*; import com.intellij.codeInsight.lookup.impl.LookupImpl; import com.intellij.codeInsight.template.*; -import com.intellij.lang.ASTNode; import com.intellij.lang.LanguageLiteralEscapers; import com.intellij.lang.injection.InjectedLanguageManager; import com.intellij.openapi.Disposable; @@ -64,10 +63,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.awt.*; -import java.util.ArrayList; -import java.util.BitSet; +import java.util.*; import java.util.List; -import java.util.Map; public class TemplateState implements Disposable { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.template.impl.TemplateState"); @@ -920,8 +917,10 @@ public class TemplateState implements Disposable { } private void initTabStopHighlighters() { + final Set vars = new HashSet(); for (int i = 0; i < myTemplate.getVariableCount(); i++) { String variableName = myTemplate.getVariableNameAt(i); + if (!vars.add(variableName)) continue; int segmentNumber = myTemplate.getVariableSegmentNumber(variableName); if (segmentNumber < 0) continue; RangeHighlighter segmentHighlighter = getSegmentHighlighter(segmentNumber, false, false); From 91e33e86aad7ea470f8b9fcb0a2b27ae31b921b0 Mon Sep 17 00:00:00 2001 From: peter Date: Fri, 21 Oct 2011 14:26:12 +0200 Subject: [PATCH 19/19] green named arguments from other calls, underlined unresolved references --- .../lookup/LookupElementBuilder.java | 17 ++++++++++-- .../lookup/LookupElementPresentation.java | 11 ++++++++ .../lookup/impl/LookupCellRenderer.java | 4 +-- .../highlighter/DefaultHighlighter.java | 5 +++- .../lang/completion/GroovyCompletionUtil.java | 27 +++++-------------- .../MapArgumentCompletionProvider.java | 14 +++++++--- 6 files changed, 48 insertions(+), 30 deletions(-) diff --git a/platform/lang-api/src/com/intellij/codeInsight/lookup/LookupElementBuilder.java b/platform/lang-api/src/com/intellij/codeInsight/lookup/LookupElementBuilder.java index 7f1b6db4c12c..45272de8f5b2 100644 --- a/platform/lang-api/src/com/intellij/codeInsight/lookup/LookupElementBuilder.java +++ b/platform/lang-api/src/com/intellij/codeInsight/lookup/LookupElementBuilder.java @@ -25,6 +25,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; +import java.awt.*; import java.util.Collections; import java.util.Set; @@ -129,6 +130,18 @@ public class LookupElementBuilder extends LookupElement { myAllLookupStrings, caseSensitive); } + public LookupElementBuilder setItemTextForeground(@NotNull Color itemTextForeground) { + final LookupElementPresentation presentation = copyPresentation(); + presentation.setItemTextForeground(itemTextForeground); + return new LookupElementBuilder(myLookupString, myObject, myInsertHandler, null, presentation, myAllLookupStrings, myCaseSensitive); + } + + public LookupElementBuilder setItemTextUnderlined(boolean underlined) { + final LookupElementPresentation presentation = copyPresentation(); + presentation.setItemTextUnderlined(underlined); + return new LookupElementBuilder(myLookupString, myObject, myInsertHandler, null, presentation, myAllLookupStrings, myCaseSensitive); + } + public LookupElementBuilder setTypeText(@Nullable String typeText) { return setTypeText(typeText, false); } @@ -162,7 +175,7 @@ public class LookupElementBuilder extends LookupElement { public LookupElementBuilder setStrikeout() { return setStrikeout(true); } - + public LookupElementBuilder setStrikeout(boolean strikeout) { final LookupElementPresentation presentation = copyPresentation(); presentation.setStrikeout(strikeout); @@ -228,7 +241,7 @@ public class LookupElementBuilder extends LookupElement { : myInsertHandler != insertHandler) return false; if (!myLookupString.equals(that.myLookupString)) return false; if (!myObject.equals(that.myObject)) return false; - + final LookupElementRenderer renderer = that.myRenderer; if (myRenderer != null && renderer != null ? !myRenderer.getClass().equals(renderer.getClass()) : myRenderer != renderer) return false; diff --git a/platform/lang-api/src/com/intellij/codeInsight/lookup/LookupElementPresentation.java b/platform/lang-api/src/com/intellij/codeInsight/lookup/LookupElementPresentation.java index 10cd7634e46e..08f0eb90752e 100644 --- a/platform/lang-api/src/com/intellij/codeInsight/lookup/LookupElementPresentation.java +++ b/platform/lang-api/src/com/intellij/codeInsight/lookup/LookupElementPresentation.java @@ -33,6 +33,7 @@ public class LookupElementPresentation { private boolean myStrikeout; private boolean myTailGrayed; private Color myTailForeground; + private Color myItemTextForeground = Color.black; private boolean myItemTextBold; private boolean myItemTextUnderlined; private boolean myTypeGrayed; @@ -136,6 +137,14 @@ public class LookupElementPresentation { myItemTextUnderlined = itemTextUnderlined; } + @NotNull public Color getItemTextForeground() { + return myItemTextForeground; + } + + public void setItemTextForeground(@NotNull Color itemTextForeground) { + myItemTextForeground = itemTextForeground; + } + public void copyFrom(@NotNull LookupElementPresentation presentation) { myIcon = presentation.myIcon; myTypeIcon = presentation.myTypeIcon; @@ -147,6 +156,8 @@ public class LookupElementPresentation { myTailForeground = presentation.myTailForeground; myItemTextBold = presentation.myItemTextBold; myTypeGrayed = presentation.myTypeGrayed; + myItemTextUnderlined = presentation.myItemTextUnderlined; + myItemTextForeground = presentation.myItemTextForeground; } public boolean isTypeGrayed() { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupCellRenderer.java b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupCellRenderer.java index 3ea685ebc0b1..282f6a05aa00 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupCellRenderer.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupCellRenderer.java @@ -49,7 +49,7 @@ public class LookupCellRenderer implements ListCellRenderer { private final FontMetrics myBoldMetrics; public static final Color BACKGROUND_COLOR = new Color(235, 244, 254); - static final Color FOREGROUND_COLOR = Color.black; + private static final Color FOREGROUND_COLOR = Color.black; private static final Color GRAYED_FOREGROUND_COLOR = new Color(160, 160, 160); private static final Color SELECTED_BACKGROUND_COLOR = new Color(0, 82, 164); private static final Color SELECTED_FOREGROUND_COLOR = Color.white; @@ -126,7 +126,7 @@ public class LookupCellRenderer implements ListCellRenderer { myNameComponent.clear(); myNameComponent.setIcon(augmentIcon(presentation.getIcon(), myEmptyIcon)); myNameComponent.setBackground(background); - allowedWidth -= setItemTextLabel(item, foreground, isSelected, presentation, allowedWidth); + allowedWidth -= setItemTextLabel(item, presentation.getItemTextForeground(), isSelected, presentation, allowedWidth); myTypeLabel.clear(); if (allowedWidth > 0) { diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/highlighter/DefaultHighlighter.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/highlighter/DefaultHighlighter.java index e8ba1f3f4142..d74011828700 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/highlighter/DefaultHighlighter.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/highlighter/DefaultHighlighter.java @@ -183,8 +183,11 @@ public class DefaultHighlighter { } public static final TextAttributes MAP_KEY_ATTRIBUTES = HighlighterColors.TEXT.getDefaultAttributes().clone(); + + public static final Color MAP_KEY_COLOR = new Color(0, 128, 0); + static { - MAP_KEY_ATTRIBUTES.setForegroundColor(new Color(0, 128, 0)); + MAP_KEY_ATTRIBUTES.setForegroundColor(MAP_KEY_COLOR); } public static TextAttributesKey UNRESOLVED_ACCESS = TextAttributesKey.createTextAttributesKey(UNRESOLVED_ACCESS_ID, UNRESOLVED_ACCESS_ATTRIBUTES); public static TextAttributesKey LITERAL_CONVERSION = TextAttributesKey.createTextAttributesKey(LITERAL_CONVERSION_ID, LITERAL_CONVERSION_ATTRIBUTES); diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/GroovyCompletionUtil.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/GroovyCompletionUtil.java index 3f99e7717c1e..e9188299ceee 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/GroovyCompletionUtil.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/GroovyCompletionUtil.java @@ -33,7 +33,6 @@ import com.intellij.openapi.util.Iconable; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; import com.intellij.psi.codeStyle.CodeStyleSettingsManager; -import com.intellij.psi.impl.light.LightMethodBuilder; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.TokenSet; import com.intellij.psi.util.InheritanceUtil; @@ -47,8 +46,6 @@ import com.intellij.util.text.CharArrayUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.plugins.groovy.GroovyFileType; -import org.jetbrains.plugins.groovy.GroovyIcons; import org.jetbrains.plugins.groovy.lang.GrReferenceAdjuster; import org.jetbrains.plugins.groovy.lang.psi.GrReferenceElement; import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement; @@ -271,30 +268,17 @@ public class GroovyCompletionUtil { return setupLookupBuilder(element, substitutor, builder); } - private static PsiMethod generateMethodInCategory(GroovyResolveResult result) { - final PsiElement element = result.getElement(); - assert element instanceof PsiMethod; - final LightMethodBuilder builder = new LightMethodBuilder(element.getManager(), GroovyFileType.GROOVY_LANGUAGE, ((PsiMethod)element).getName()); - final PsiParameter[] params = ((PsiMethod)element).getParameterList().getParameters(); - for (int i = 1; i < params.length; i++) { - builder.addParameter(params[i]); - } - builder.setBaseIcon(GroovyIcons.METHOD); - return builder; - } - public static LookupElement getLookupElement(Object o) { if (o instanceof LookupElement) return (LookupElement)o; if (o instanceof PsiNamedElement) return generateLookupElement((PsiNamedElement)o); if (o instanceof PsiElement) return setupLookupBuilder((PsiElement)o, PsiSubstitutor.EMPTY, LookupElementBuilder.create(o, ((PsiElement)o).getText())); - return LookupElementBuilder.create(o, o.toString()); + return LookupElementBuilder.create(o, o.toString()).setItemTextUnderlined(true); } - public static LookupElementBuilder generateLookupElement(PsiNamedElement element) { - LookupElementBuilder builder = LookupElementBuilder.create(element); - return setupLookupBuilder(element, PsiSubstitutor.EMPTY, builder); + private static LookupElementBuilder generateLookupElement(PsiNamedElement element) { + return setupLookupBuilder(element, PsiSubstitutor.EMPTY, LookupElementBuilder.create(element)); } - public static LookupElementBuilder setupLookupBuilder(PsiElement element, PsiSubstitutor substitutor, LookupElementBuilder builder) { + private static LookupElementBuilder setupLookupBuilder(PsiElement element, PsiSubstitutor substitutor, LookupElementBuilder builder) { builder = builder.setIcon(element.getIcon(Iconable.ICON_FLAG_VISIBILITY | Iconable.ICON_FLAG_READ_STATUS)) .setInsertHandler(GroovyInsertHandler.INSTANCE); builder = setTailText(element, builder, substitutor); @@ -426,9 +410,10 @@ public class GroovyCompletionUtil { } //need to shorten references in type argument list - public static void shortenReference(final PsiFile file, final int offset) throws IncorrectOperationException { + private static void shortenReference(final PsiFile file, final int offset) throws IncorrectOperationException { final PsiDocumentManager manager = PsiDocumentManager.getInstance(file.getProject()); final Document document = manager.getDocument(file); + assert document != null; manager.commitDocument(document); final PsiReference ref = file.findReferenceAt(offset); if (ref instanceof GrCodeReferenceElement) { diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/MapArgumentCompletionProvider.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/MapArgumentCompletionProvider.java index 199b3ac5a207..ec3dbcac2184 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/MapArgumentCompletionProvider.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/MapArgumentCompletionProvider.java @@ -16,7 +16,6 @@ package org.jetbrains.plugins.groovy.lang.completion; import com.intellij.codeInsight.completion.*; -import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.codeInsight.lookup.LookupElementBuilder; import com.intellij.patterns.ElementPattern; import com.intellij.patterns.StandardPatterns; @@ -26,6 +25,7 @@ import com.intellij.util.ProcessingContext; import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.groovy.GroovyIcons; import org.jetbrains.plugins.groovy.extensions.GroovyNamedArgumentProvider; +import org.jetbrains.plugins.groovy.highlighter.DefaultHighlighter; import org.jetbrains.plugins.groovy.lang.completion.handlers.NamedArgumentInsertHandler; import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes; import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.GrListOrMap; @@ -89,7 +89,8 @@ class MapArgumentCompletionProvider extends CompletionProvider map = calcNamedArgumentsForCall(mapOrArgumentList); - if (map.isEmpty()) { + boolean sure = !map.isEmpty(); + if (!sure) { map = findOtherNamedArgumentsInFile(mapOrArgumentList); } @@ -98,11 +99,16 @@ class MapArgumentCompletionProvider extends CompletionProvider entry : map.entrySet()) { - LookupElement lookup = LookupElementBuilder.create(entry.getValue(), entry.getKey()) - .setIcon(GroovyIcons.DYNAMIC) + LookupElementBuilder lookup = LookupElementBuilder.create(entry.getValue(), entry.getKey()) .setInsertHandler(NamedArgumentInsertHandler.INSTANCE) .setTailText(":"); + if (sure) { + lookup = lookup.setIcon(GroovyIcons.DYNAMIC); + } else { + lookup = lookup.setItemTextForeground(DefaultHighlighter.MAP_KEY_COLOR); + } + result.addElement(lookup); }