From e03ef653eafbcf2ded0941ed47423f1abd6bc254 Mon Sep 17 00:00:00 2001 From: peter Date: Wed, 19 Sep 2012 20:03:43 +0400 Subject: [PATCH 01/12] diagnose invalid type during substitution (EA-38796) --- .../src/com/intellij/psi/impl/PsiSubstitutorImpl.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/PsiSubstitutorImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/PsiSubstitutorImpl.java index c0d5deac91e6..22dd11065416 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/PsiSubstitutorImpl.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/PsiSubstitutorImpl.java @@ -93,7 +93,11 @@ public class PsiSubstitutorImpl implements PsiSubstitutor { @Override public PsiType substitute(PsiType type) { - if (type == null) return null; + if (type == null) { + //noinspection ConstantConditions + return null; + } + assert type.isValid(); PsiType substituted = type.accept(myAddingBoundsSubstitutionVisitor); return correctExternalSubstitution(substituted, type); } @@ -148,6 +152,7 @@ public class PsiSubstitutorImpl implements PsiSubstitutor { if (newBound == null) { return null; } + assert newBound.isValid() : newBound.getClass(); if (newBound instanceof PsiWildcardType) { return handleBoundComposition(wildcardType, (PsiWildcardType)newBound); } From 4666a9357f256bd9a4bb31cc4575aacdd7db4542 Mon Sep 17 00:00:00 2001 From: peter Date: Wed, 19 Sep 2012 20:05:37 +0400 Subject: [PATCH 02/12] yet another validity check in pase (EA-39016) --- .../src/com/intellij/ide/actions/JavaQualifiedNameProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/java-impl/src/com/intellij/ide/actions/JavaQualifiedNameProvider.java b/java/java-impl/src/com/intellij/ide/actions/JavaQualifiedNameProvider.java index d340460bb645..4f49420e6b5f 100644 --- a/java/java-impl/src/com/intellij/ide/actions/JavaQualifiedNameProvider.java +++ b/java/java-impl/src/com/intellij/ide/actions/JavaQualifiedNameProvider.java @@ -220,7 +220,7 @@ public class JavaQualifiedNameProvider implements QualifiedNameProvider { catch (IncorrectOperationException e) { // failed to bind } - if (!isReferencedTo(referenceExpression, targetElement)) { + if (referenceExpression.isValid() && !isReferencedTo(referenceExpression, targetElement)) { toInsert = fqn; } } From 953d68cd56cf1c3c1fe1c24212be1ad4b47a49ef Mon Sep 17 00:00:00 2001 From: peter Date: Wed, 19 Sep 2012 20:08:58 +0400 Subject: [PATCH 03/12] yet another validity check in pase (EA-39233) --- .../completion/JavaMemberNameCompletionContributor.java | 1 + 1 file changed, 1 insertion(+) diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaMemberNameCompletionContributor.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaMemberNameCompletionContributor.java index 48d86aeba882..dc49afd657a6 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaMemberNameCompletionContributor.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaMemberNameCompletionContributor.java @@ -375,6 +375,7 @@ public class JavaMemberNameCompletionContributor extends CompletionContributor { for (final PsiField field : fields) { if (field == element) continue; + assert field.isValid(); final PsiModifierList modifierList = field.getModifierList(); if (staticContext && (modifierList != null && !modifierList.hasModifierProperty(PsiModifier.STATIC))) continue; From f3a8032b544db612431b1cb24cfd8e43af2e58b1 Mon Sep 17 00:00:00 2001 From: peter Date: Thu, 20 Sep 2012 16:04:57 +0400 Subject: [PATCH 04/12] partial type dfa in groovy: infer only the types that are needed to resolve the current expression --- .../dataFlow/types/TypeInferenceHelper.java | 191 ++++++++++++++---- .../psi/dataFlow/types/TypesSemilattice.java | 25 ++- .../lang/GroovyStressPerformanceTest.groovy | 2 +- 3 files changed, 177 insertions(+), 41 deletions(-) diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/dataFlow/types/TypeInferenceHelper.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/dataFlow/types/TypeInferenceHelper.java index 1b510ed6ecfa..08679aef8320 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/dataFlow/types/TypeInferenceHelper.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/dataFlow/types/TypeInferenceHelper.java @@ -114,7 +114,8 @@ public class TypeInferenceHelper { final GrControlFlowOwner scope = ControlFlowUtils.findControlFlowOwner(refExpr); if (scope == null) return null; - return inferVariableTypes(scope).getInferredType(refExpr.getReferenceName(), ControlFlowUtils.findRWInstruction(refExpr, scope.getControlFlow())); + return getInferenceCache(scope).getInferredType(refExpr.getReferenceName(), ControlFlowUtils + .findRWInstruction(refExpr, scope.getControlFlow())); } @Nullable @@ -122,18 +123,16 @@ public class TypeInferenceHelper { final GrControlFlowOwner scope = ControlFlowUtils.findControlFlowOwner(place); if (scope == null) return null; - return inferVariableTypes(scope).getInferredType(variableName, ControlFlowUtils.findNearestInstruction(place, scope.getControlFlow())); + return getInferenceCache(scope).getInferredType(variableName, ControlFlowUtils.findNearestInstruction(place, scope.getControlFlow())); } @NotNull - private static InferenceResult inferVariableTypes(final GrControlFlowOwner scope) { - return CachedValuesManager.getManager(scope.getProject()).getCachedValue(scope, new CachedValueProvider() { + private static InferenceCache getInferenceCache(final GrControlFlowOwner scope) { + return CachedValuesManager.getManager(scope.getProject()).getCachedValue(scope, new CachedValueProvider() { @Nullable @Override - public Result compute() { - Instruction[] flow = scope.getControlFlow(); - List> list = performTypeDfa(scope, flow); - return Result.create(new InferenceResult(flow, list), PsiModificationTracker.MODIFICATION_COUNT); + public Result compute() { + return Result.create(new InferenceCache(scope), PsiModificationTracker.MODIFICATION_COUNT); } }); } @@ -299,27 +298,17 @@ public class TypeInferenceHelper { return null; } - @Nullable - private static List> performTypeDfa(GrControlFlowOwner owner, Instruction[] flow) { - final TypeDfaInstance dfaInstance = new TypeDfaInstance(owner, flow); - final TypesSemilattice semilattice = new TypesSemilattice(owner.getManager()); - List states = new DFAEngine(flow, dfaInstance, semilattice).performDFAWithTimeout(); - if (states == null) return null; - - List> result = ContainerUtil.newArrayList(); - for (int i = 0; i < states.size(); i++) { - result.add(states.get(i).getBindings(flow[i])); - } - return result; - } - static class TypeDfaInstance implements DfaInstance { private final GrControlFlowOwner myScope; private final Instruction[] myFlow; + private final Set myInteresting; + private final InferenceCache myCache; - TypeDfaInstance(GrControlFlowOwner scope, Instruction[] flow) { + TypeDfaInstance(GrControlFlowOwner scope, Instruction[] flow, Set interesting, InferenceCache cache) { myScope = scope; myFlow = flow; + myInteresting = interesting; + myCache = cache; } public void fun(final TypeDfaState state, final Instruction instruction) { @@ -335,31 +324,48 @@ public class TypeInferenceHelper { final String varName = instruction.getVariableName(); if (varName == null) return; - state.putType(varName, doInference(state.getBindings(instruction), new NullableComputable() { + updateVariableType(state, instruction, varName, new NullableComputable() { @Override public DFAType compute() { ReadWriteVariableInstruction originalInstr = instruction.getInstructionToMixin(myFlow); assert originalInstr != null && !originalInstr.isWrite(); - DFAType original = state.getVariableType(varName).negate(originalInstr); + DFAType original = state.getVariableType(varName); + if (original == null) { + original = DFAType.create(null); + } + original = original.negate(originalInstr); original.addMixin(instruction.inferMixinType(), instruction.getConditionInstruction()); return original; } - })); + }); } private void handleVariableWrite(TypeDfaState state, ReadWriteVariableInstruction instruction) { final PsiElement element = instruction.getElement(); if (element != null && instruction.isWrite()) { - state.putType(instruction.getVariableName(), doInference(state.getBindings(instruction), new Computable() { + updateVariableType(state, instruction, instruction.getVariableName(), new Computable() { @Override public DFAType compute() { return DFAType.create(TypesUtil.boxPrimitiveType(getInitializerType(element), myScope.getManager(), myScope.getResolveScope())); } - })); + }); } } + private void updateVariableType(TypeDfaState state, Instruction instruction, String variableName, Computable computation) { + if (!myInteresting.contains(instruction)) { + state.removeBinding(variableName); + return; + } + + DFAType type = myCache.getCachedInferredType(variableName, instruction); + if (type == null) { + type = doInference(state.getBindings(instruction), computation); + } + state.putType(variableName, type); + } + @NotNull public TypeDfaState initial() { return new TypeDfaState(); @@ -371,22 +377,135 @@ public class TypeInferenceHelper { } - private static class InferenceResult { + private static class InferenceCache { + final GrControlFlowOwner scope; final Instruction[] flow; - final List> varTypes; + final AtomicReference> varTypes; + final ConcurrentHashSet tooComplex = new ConcurrentHashSet(); - InferenceResult(Instruction[] flow, @Nullable List> varTypes) { - this.flow = flow; - this.varTypes = varTypes; + InferenceCache(final GrControlFlowOwner scope) { + this.scope = scope; + this.flow = scope.getControlFlow(); + List noTypes = new ArrayList(); + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < flow.length; i++) { + noTypes.add(new TypeDfaState()); + } + varTypes = new AtomicReference>(noTypes); } @Nullable - private PsiType getInferredType(String variableName, Instruction instruction) { - if (instruction == null || varTypes == null) return null; + private PsiType getInferredType(@Nullable String variableName, @Nullable Instruction instruction) { + if (instruction == null || variableName == null) return null; + if (tooComplex.contains(instruction)) return null; - return varTypes.get(instruction.num()).get(variableName); + TypeDfaState cache = varTypes.get().get(instruction.num()); + if (!cache.containsVariable(variableName)) { + Set interesting = collectRequiredInstructions(instruction, variableName); + List dfaResult = performTypeDfa(scope, flow, interesting); + if (dfaResult == null) { + tooComplex.addAll(interesting); + } else { + cacheDfaResult(dfaResult); + } + } + DFAType dfaType = getCachedInferredType(variableName, instruction); + return dfaType == null ? null : dfaType.getResultType(); } + @Nullable + private List performTypeDfa(GrControlFlowOwner owner, Instruction[] flow, Set interesting) { + final TypeDfaInstance dfaInstance = new TypeDfaInstance(owner, flow, interesting, this); + final TypesSemilattice semilattice = new TypesSemilattice(owner.getManager()); + return new DFAEngine(flow, dfaInstance, semilattice).performDFAWithTimeout(); + } + + @Nullable + DFAType getCachedInferredType(@NotNull String variableName, @NotNull Instruction instruction) { + DFAType dfaType = varTypes.get().get(instruction.num()).getVariableType(variableName); + return dfaType == null ? null : dfaType.negate(instruction); + } + + private Set collectRequiredInstructions(Instruction instruction, String variableName) { + Pair> defUse = getDefUseMaps(scope); + + Set interesting = ContainerUtil.newHashSet(instruction); + LinkedList> queue = ContainerUtil.newLinkedList(); + queue.add(Pair.create(instruction, variableName)); + while (!queue.isEmpty()) { + Pair pair = queue.removeFirst(); + for (Pair dep : findDependencies(defUse, pair.first, pair.second)) { + if (interesting.add(dep.first)) { + queue.addLast(dep); + } + } + } + + return interesting; + } + + private Set> findDependencies(Pair> defUse, + Instruction insn, + String varName) { + int[] definitions = defUse.second.get(insn.num()).getDefinitions(defUse.first.getVarIndex(varName)); + if (definitions == null) return Collections.emptySet(); + + HashSet> pairs = ContainerUtil.newHashSet(); + for (int defIndex : definitions) { + Instruction write = flow[defIndex]; + pairs.add(Pair.create(write, varName)); + PsiElement statement = findDependencyScope(write.getElement()); + pairs.addAll(findAllInstructionsInside(statement)); + } + return pairs; + } + + private List> findAllInstructionsInside(PsiElement scope) { + final List> result = ContainerUtil.newArrayList(); + scope.accept(new PsiRecursiveElementWalkingVisitor() { + @Override + public void visitElement(PsiElement element) { + if (element instanceof GrReferenceExpression && !((GrReferenceExpression)element).isQualified()) { + String varName = ((GrReferenceExpression)element).getReferenceName(); + if (varName != null) { + for (Instruction dependency : ControlFlowUtils.findAllInstructions(element, flow)) { + result.add(Pair.create(dependency, varName)); + } + } + } + super.visitElement(element); + } + }); + return result; + } + + @Nullable + private static PsiElement findDependencyScope(PsiElement element) { + return PsiTreeUtil.findFirstParent(element, new Condition() { + @Override + public boolean value(PsiElement element) { + return org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil.isExpressionStatement(element) || + !(element.getParent() instanceof GrExpression); + } + }); + } + + private void cacheDfaResult(List dfaResult) { + while (true) { + List oldTypes = varTypes.get(); + if (varTypes.compareAndSet(oldTypes, addDfaResult(dfaResult, oldTypes))) { + return; + } + } + } + + private static List addDfaResult(List dfaResult, List oldTypes) { + List newTypes = new ArrayList(oldTypes); + for (int i = 0; i < dfaResult.size(); i++) { + newTypes.set(i, newTypes.get(i).mergeWith(dfaResult.get(i))); + } + return newTypes; + } } } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/dataFlow/types/TypesSemilattice.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/dataFlow/types/TypesSemilattice.java index d5d49e79e810..f5b29121883e 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/dataFlow/types/TypesSemilattice.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/dataFlow/types/TypesSemilattice.java @@ -19,6 +19,7 @@ import com.intellij.psi.PsiManager; import com.intellij.psi.PsiType; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.lang.psi.controlFlow.Instruction; import org.jetbrains.plugins.groovy.lang.psi.dataFlow.DFAType; import org.jetbrains.plugins.groovy.lang.psi.dataFlow.Semilattice; @@ -63,6 +64,15 @@ class TypeDfaState { myVarTypes = ContainerUtil.newHashMap(another.myVarTypes); } + TypeDfaState mergeWith(TypeDfaState another) { + if (another.myVarTypes.isEmpty()) { + return this; + } + TypeDfaState state = new TypeDfaState(this); + state.myVarTypes.putAll(another.myVarTypes); + return state; + } + void joinState(TypeDfaState another, PsiManager manager) { for (Map.Entry entry : another.myVarTypes.entrySet()) { final String name = entry.getKey(); @@ -83,10 +93,9 @@ class TypeDfaState { return myVarTypes.equals(another.myVarTypes); } - @NotNull + @Nullable DFAType getVariableType(String variableName) { - DFAType type = myVarTypes.get(variableName); - return type == null ? DFAType.create(null) : type; + return myVarTypes.get(variableName); } Map getBindings(Instruction instruction) { @@ -98,7 +107,7 @@ class TypeDfaState { return map; } - void putType(String variableName, DFAType type) { + void putType(String variableName, @Nullable DFAType type) { myVarTypes.put(variableName, type); } @@ -106,4 +115,12 @@ class TypeDfaState { public String toString() { return "TypeDfaState{" + myVarTypes + '}'; } + + public boolean containsVariable(@NotNull String variableName) { + return myVarTypes.containsKey(variableName); + } + + public void removeBinding(String variableName) { + myVarTypes.remove(variableName); + } } \ No newline at end of file diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GroovyStressPerformanceTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GroovyStressPerformanceTest.groovy index 83dd3971f2a8..d9b079615b98 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GroovyStressPerformanceTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GroovyStressPerformanceTest.groovy @@ -207,7 +207,7 @@ class SomeClass { measureHighlighting(text, 8000) } - public void "_test infer only the variable types that are needed"() { + public void "test infer only the variable types that are needed"() { addGdsl '''contribute(currentType(String.name)) { println 'sleeping' Thread.sleep(1000) From 543091ee3f42d26d454c6a0cca4eb2b8250a1610 Mon Sep 17 00:00:00 2001 From: peter Date: Thu, 20 Sep 2012 16:48:18 +0400 Subject: [PATCH 05/12] thread user time checks are expensive, do them less frequently --- .../jetbrains/plugins/groovy/lang/psi/dataFlow/DFAEngine.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/dataFlow/DFAEngine.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/dataFlow/DFAEngine.java index d6845a3bbde7..891284a1d7fd 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/dataFlow/DFAEngine.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/dataFlow/DFAEngine.java @@ -103,7 +103,7 @@ public class DFAEngine { while (!workList.isEmpty()) { count++; - if (timeout && count % 50 == 0 && tm.getCurrentThreadUserTime() - startTime > ourTimeLimit) return null; + if (timeout && count % 512 == 0 && tm.getCurrentThreadUserTime() - startTime > ourTimeLimit) return null; ProgressManager.checkCanceled(); final Instruction curr = workList.remove(); From 5a5580db12f6280a4f597ceef518788bc7966be9 Mon Sep 17 00:00:00 2001 From: peter Date: Thu, 20 Sep 2012 16:48:39 +0400 Subject: [PATCH 06/12] faster DFAType equals & lub --- .../jetbrains/plugins/groovy/lang/psi/dataFlow/DFAType.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/dataFlow/DFAType.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/dataFlow/DFAType.java index 2bb36b1df599..fe974375dbe4 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/dataFlow/DFAType.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/dataFlow/DFAType.java @@ -88,6 +88,7 @@ public class DFAType { @Override public boolean equals(Object obj) { + if (obj == this) return true; if (!(obj instanceof DFAType)) return false; final DFAType other = (DFAType)obj; @@ -151,11 +152,13 @@ public class DFAType { } private static boolean eq(PsiType t1, PsiType t2) { - return Comparing.equal(TypeConversionUtil.erasure(t1), TypeConversionUtil.erasure(t2)); + return t1 == t2 || Comparing.equal(TypeConversionUtil.erasure(t1), TypeConversionUtil.erasure(t2)); } @Nullable public static DFAType create(DFAType t1, DFAType t2, PsiManager manager) { + if (t1.equals(t2)) return t1; + final PsiType primary = TypesUtil.getLeastUpperBoundNullable(t1.primary, t2.primary, manager); final DFAType type = new DFAType(primary); From 73f67d9283cca170c64d85607c2149e02dfb0fed Mon Sep 17 00:00:00 2001 From: peter Date: Thu, 20 Sep 2012 17:10:29 +0400 Subject: [PATCH 07/12] cache class all methods --- .../groovy/lang/psi/util/GrClassImplUtil.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/util/GrClassImplUtil.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/util/GrClassImplUtil.java index 2b2293505cad..a62f2b30c225 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/util/GrClassImplUtil.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/util/GrClassImplUtil.java @@ -26,9 +26,8 @@ import com.intellij.psi.impl.PsiClassImplUtil; import com.intellij.psi.infos.CandidateInfo; import com.intellij.psi.scope.NameHint; import com.intellij.psi.scope.PsiScopeProcessor; -import com.intellij.psi.util.MethodSignature; +import com.intellij.psi.util.*; import com.intellij.psi.util.PsiUtil; -import com.intellij.psi.util.TypeConversionUtil; import com.intellij.util.ArrayUtil; import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; @@ -207,11 +206,16 @@ public class GrClassImplUtil { } @NotNull - public static PsiMethod[] getAllMethods(GrTypeDefinition grType) { - List allMethods = new ArrayList(); - getAllMethodsInner(grType, allMethods, new HashSet()); - - return allMethods.toArray(new PsiMethod[allMethods.size()]); + public static PsiMethod[] getAllMethods(final GrTypeDefinition grType) { + return CachedValuesManager.getManager(grType.getProject()).getCachedValue(grType, new CachedValueProvider() { + @Nullable + @Override + public Result compute() { + List list = ContainerUtil.newArrayList(); + getAllMethodsInner(grType, list, new HashSet()); + return Result.create(list.toArray(new PsiMethod[list.size()]), PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT, grType); + } + }); } @NotNull From 81a85ad8211d53c8b91b37f4207a4d25a41b8f09 Mon Sep 17 00:00:00 2001 From: peter Date: Thu, 20 Sep 2012 17:57:05 +0400 Subject: [PATCH 08/12] faster groovy import declaration processing --- .../imports/GrImportStatementImpl.java | 172 ++++++++++++------ 1 file changed, 113 insertions(+), 59 deletions(-) diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/toplevel/imports/GrImportStatementImpl.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/toplevel/imports/GrImportStatementImpl.java index 0add5d58871a..c9ef96991d7d 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/toplevel/imports/GrImportStatementImpl.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/toplevel/imports/GrImportStatementImpl.java @@ -20,7 +20,11 @@ import com.intellij.lang.ASTNode; import com.intellij.psi.*; import com.intellij.psi.scope.NameHint; import com.intellij.psi.scope.PsiScopeProcessor; +import com.intellij.psi.util.CachedValueProvider; +import com.intellij.psi.util.CachedValuesManager; +import com.intellij.psi.util.PsiModificationTracker; import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes; @@ -36,6 +40,8 @@ import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil; import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil; import org.jetbrains.plugins.groovy.lang.resolve.processors.ResolverProcessor; +import java.util.List; + /** * @author ilyas */ @@ -82,56 +88,115 @@ public class GrImportStatementImpl extends GroovyPsiElementImpl implements GrImp if (ref == null) return true; if (isStatic()) { - GrCodeReferenceElement qualifier = ref.getQualifier(); - if (qualifier == null) return true; - PsiElement resolved = qualifier.resolve(); - if (!(resolved instanceof PsiClass)) return true; - PsiClass clazz = (PsiClass)resolved; - - state = state.put(ResolverProcessor.RESOLVE_CONTEXT, this); - - final String refName = ref.getReferenceName(); - if (nameHint == null || name.equals(nameHint.getName(state))) { - final PsiField field = clazz.findFieldByName(refName, true); - if (field != null && field.hasModifierProperty(PsiModifier.STATIC)) { - if (!processor.execute(field, state)) return false; - } - - for (PsiMethod method : clazz.findMethodsByName(refName, true)) { - if (method.hasModifierProperty(PsiModifier.STATIC)) { - if (!processor.execute(method, state)) return false; - } - } - - final PsiClass innerClass = clazz.findInnerClassByName(refName, true); - if (innerClass != null && innerClass.hasModifierProperty(PsiModifier.STATIC) && !processor.execute(innerClass, state)) return false; - } - - final PsiMethod getter = GroovyPropertyUtils.findPropertyGetter(clazz, refName, true, true); - if (getter != null && - (nameHint == null || name.equals(GroovyPropertyUtils.getPropertyNameByGetterName(nameHint.getName(state), true)))) { - if (!processor.execute(getter, state)) return false; - } - - final PsiMethod setter = GroovyPropertyUtils.findPropertySetter(clazz, refName, true, true); - if (setter != null && - (nameHint == null || name.equals(GroovyPropertyUtils.getPropertyNameBySetterName(nameHint.getName(state))))) { - if (!processor.execute(setter, state)) return false; - } + return processSingleStaticImport(processor, state, name, nameHint, ref); } - else { //class import statement - if (nameHint == null || name.equals(nameHint.getName(state))) { - final PsiElement resolved = ref.resolve(); - if (resolved instanceof PsiClass) { - if (!isAliasedImport() && isFromSamePackage((PsiClass)resolved)) return true; //don't process classes from the same package because such import statements are ignored by compiler - state = state.put(ResolverProcessor.RESOLVE_CONTEXT, this); - if (!processor.execute(resolved, state)) return false; - } - } + if (nameHint == null || name.equals(nameHint.getName(state))) { + return processSingleClassImport(processor, state, ref); } return true; } + @Nullable + private PsiClass resolveQualifier() { + return CachedValuesManager.getManager(getProject()).getCachedValue(this, new CachedValueProvider() { + @Nullable + @Override + public Result compute() { + GrCodeReferenceElement reference = getImportReference(); + GrCodeReferenceElement qualifier = reference == null ? null : reference.getQualifier(); + PsiElement target = qualifier == null ? null : qualifier.resolve(); + PsiClass clazz = target instanceof PsiClass ? (PsiClass)target : null; + return Result.create(clazz, PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT, GrImportStatementImpl.this); + } + }); + } + + private static List getAllStaticMembers(final PsiClass clazz) { + return CachedValuesManager.getManager(clazz.getProject()).getCachedValue(clazz, new CachedValueProvider>() { + @Nullable + @Override + public Result> compute() { + List result = ContainerUtil.newArrayList(); + for (PsiMethod method : clazz.getAllMethods()) { + if (method.hasModifierProperty(PsiModifier.STATIC)) { + result.add(method); + } + } + for (PsiField field : clazz.getAllFields()) { + if (field.hasModifierProperty(PsiModifier.STATIC)) { + result.add(field); + } + } + for (PsiClass inner : clazz.getAllInnerClasses()) { + if (inner.hasModifierProperty(PsiModifier.STATIC)) { + result.add(inner); + } + } + return Result.create(result, PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT, clazz); + } + }); + } + + private boolean processSingleStaticImport(PsiScopeProcessor processor, + ResolveState state, + String importedName, + NameHint nameHint, + GrCodeReferenceElement ref) { + PsiClass clazz = resolveQualifier(); + if (clazz == null) return true; + + state = state.put(ResolverProcessor.RESOLVE_CONTEXT, this); + String hintName = nameHint == null ? null : nameHint.getName(state); + + final String refName = ref.getReferenceName(); + if (hintName == null || importedName.equals(hintName)) { + final PsiField field = clazz.findFieldByName(refName, true); + if (field != null && field.hasModifierProperty(PsiModifier.STATIC)) { + if (!processor.execute(field, state)) return false; + } + + for (PsiMethod method : clazz.findMethodsByName(refName, true)) { + if (method.hasModifierProperty(PsiModifier.STATIC)) { + if (!processor.execute(method, state)) return false; + } + } + + final PsiClass innerClass = clazz.findInnerClassByName(refName, true); + if (innerClass != null && innerClass.hasModifierProperty(PsiModifier.STATIC) && !processor.execute(innerClass, state)) return false; + } + + String propByGetter = hintName == null ? null : GroovyPropertyUtils.getPropertyNameByGetterName(hintName, true); + String propBySetter = hintName == null ? null : GroovyPropertyUtils.getPropertyNameBySetterName(hintName); + for (PsiMember member : getAllStaticMembers(clazz)) { + if (!(member instanceof PsiMethod)) { + continue; + } + + PsiMethod method = (PsiMethod)member; + if ((propByGetter == null || importedName.equals(propByGetter)) && GroovyPropertyUtils.isSimplePropertyGetter(method, refName) || + (propBySetter == null || importedName.equals(propBySetter)) && GroovyPropertyUtils.isSimplePropertySetter(method, refName)) { + if (method.hasModifierProperty(PsiModifier.STATIC) && !processor.execute(method, state)) { + return false; + } + } + } + + return true; + } + + private boolean processSingleClassImport(PsiScopeProcessor processor, ResolveState state, GrCodeReferenceElement ref) { + final PsiElement resolved = ref.resolve(); + if (!(resolved instanceof PsiClass)) { + return true; + } + + if (!isAliasedImport() && isFromSamePackage((PsiClass)resolved)) { + return true; //don't process classes from the same package because such import statements are ignored by compiler + } + + return processor.execute(resolved, state.put(ResolverProcessor.RESOLVE_CONTEXT, this)); + } + private boolean isFromSamePackage(PsiClass resolved) { final String qualifiedName = resolved.getQualifiedName(); final String packageName = ((GroovyFile)getContainingFile()).getPackageName(); @@ -168,18 +233,9 @@ public class GrImportStatementImpl extends GroovyPsiElementImpl implements GrImp } private static boolean processAllMembers(PsiScopeProcessor processor, PsiClass clazz, ResolveState state) { - for (PsiField field : clazz.getAllFields()) { - if (field.hasModifierProperty(PsiModifier.STATIC) && !ResolveUtil.processElement(processor, field, state)) return false; + for (PsiMember member : getAllStaticMembers(clazz)) { + if (!ResolveUtil.processElement(processor, (PsiNamedElement)member, state)) return false; } - - for (PsiMethod method : clazz.getAllMethods()) { - if (method.hasModifierProperty(PsiModifier.STATIC) && !ResolveUtil.processElement(processor, method, state)) return false; - } - - for (PsiClass inner : clazz.getAllInnerClasses()) { - if (inner.hasModifierProperty(PsiModifier.STATIC) && !ResolveUtil.processElement(processor, inner, state)) return false; - } - return true; } @@ -228,9 +284,7 @@ public class GrImportStatementImpl extends GroovyPsiElementImpl implements GrImp resolved = ref.resolve(); } else { - final GrCodeReferenceElement qualifier = ref.getQualifier(); - if (qualifier == null) return null; - resolved = qualifier.resolve(); + resolved = resolveQualifier(); } return resolved instanceof PsiClass ? (PsiClass)resolved : null; From 979ca83fafdc58573472575558e4f68ef09e19f4 Mon Sep 17 00:00:00 2001 From: Rustam Vishnyakov Date: Thu, 20 Sep 2012 18:40:35 +0400 Subject: [PATCH 09/12] Another attempt to fix PropertiesFoldingTest test data path. --- .../intellij/lang/properties/PropertiesFoldingTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/properties/testSrc/com/intellij/lang/properties/PropertiesFoldingTest.java b/plugins/properties/testSrc/com/intellij/lang/properties/PropertiesFoldingTest.java index 604499389b1f..a1c74a93865f 100644 --- a/plugins/properties/testSrc/com/intellij/lang/properties/PropertiesFoldingTest.java +++ b/plugins/properties/testSrc/com/intellij/lang/properties/PropertiesFoldingTest.java @@ -15,6 +15,7 @@ */ package com.intellij.lang.properties; +import com.intellij.openapi.application.PluginPathManager; import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase; /** @@ -22,10 +23,9 @@ import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCa */ public class PropertiesFoldingTest extends LightPlatformCodeInsightFixtureTestCase { - private static final String BASE_PATH = "/plugins/properties/testData/propertiesFile/folding/"; - - protected String getBasePath() { - return BASE_PATH; + @Override + protected String getTestDataPath() { + return PluginPathManager.getPluginHomePath("properties") + "/testData/propertiesFile/folding/"; } private void doTest() { From 1c0c2c2b6c2e8454c15ae673cc5e6f11a6a38611 Mon Sep 17 00:00:00 2001 From: "Denis.Zhdanov" Date: Thu, 20 Sep 2012 18:42:35 +0400 Subject: [PATCH 10/12] IDEA-85796 shortcut for generating javadoc 'Generate doc comment' is supported for java and groovy --- .../lang/java/JavaDocumentationProvider.java | 12 + .../editorActions/FixDocCommentTest.groovy | 89 ++++++ .../CodeDocumentationProvider.java | 25 ++ .../editorActions/FixDocCommentAction.java | 253 ++++++++++++++++++ .../src/messages/ActionsBundle.properties | 2 + .../src/idea/LangActions.xml | 2 + .../GroovyDocumentationProvider.java | 13 + 7 files changed, 396 insertions(+) create mode 100644 java/java-tests/testSrc/com/intellij/codeInsight/editorActions/FixDocCommentTest.groovy create mode 100644 platform/lang-impl/src/com/intellij/codeInsight/editorActions/FixDocCommentAction.java diff --git a/java/java-impl/src/com/intellij/lang/java/JavaDocumentationProvider.java b/java/java-impl/src/com/intellij/lang/java/JavaDocumentationProvider.java index d1d8cb539902..1e5f07f6385a 100644 --- a/java/java-impl/src/com/intellij/lang/java/JavaDocumentationProvider.java +++ b/java/java-impl/src/com/intellij/lang/java/JavaDocumentationProvider.java @@ -34,6 +34,7 @@ import com.intellij.openapi.module.ModuleUtil; import com.intellij.openapi.project.IndexNotReadyException; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.*; +import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.JarFileSystem; @@ -391,6 +392,17 @@ public class JavaDocumentationProvider implements CodeDocumentationProvider, Ext return null; } + @Nullable + @Override + public Pair parseContext(@NotNull PsiElement startPoint) { + for (PsiElement e = startPoint; e != null; e = e.getParent()) { + if (e instanceof PsiDocCommentOwner) { + return Pair.create(e, ((PsiDocCommentOwner)e).getDocComment()); + } + } + return null; + } + @Override public String generateDocumentationContentStub(PsiComment _comment) { final PsiDocCommentOwner commentOwner = ((PsiDocComment)_comment).getOwner(); diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/editorActions/FixDocCommentTest.groovy b/java/java-tests/testSrc/com/intellij/codeInsight/editorActions/FixDocCommentTest.groovy new file mode 100644 index 000000000000..ef0a53746c16 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/codeInsight/editorActions/FixDocCommentTest.groovy @@ -0,0 +1,89 @@ +/* + * Copyright 2000-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.codeInsight.editorActions + +import com.intellij.openapi.editor.impl.AbstractEditorProcessingOnDocumentModificationTest + +/** + * @author Denis Zhdanov + * @since 9/20/12 6:17 PM + */ +class FixDocCommentTest extends AbstractEditorProcessingOnDocumentModificationTest { + + void testGenerateMethodDoc() { + doTest( + initial: '''\ +class Test { + String test(int i) { + return "s"; + } +}''', + expected: '''\ +class Test { + /** + * + * @param i + * @return + */ + String test(int i) { + return "s"; + } +}''' + ) + } + + void testGenerateFieldDoc() { + doTest( + initial: '''\ +class Test { + int i; +}''', + expected: '''\ +class Test { + /** + * + */ + int i; +}''' + ) + } + + void testGenerateClassDoc() { + doTest( + initial: '''\ +class Test { + void test1() {} + + void test2() {} +}''', + expected: '''\ +/** + * + */ +class Test { + void test1() {} + + void test2() {} +}''' + ) + } + + private def doTest(Map args) { + configureFromFileText("${getTestName(false)}.java", args.initial) + executeAction(FixDocCommentAction.ACTION_ID) + checkResultByText(args.expected) + } +} diff --git a/platform/lang-api/src/com/intellij/lang/documentation/CodeDocumentationProvider.java b/platform/lang-api/src/com/intellij/lang/documentation/CodeDocumentationProvider.java index 1064cd7ef119..806ef07e470e 100644 --- a/platform/lang-api/src/com/intellij/lang/documentation/CodeDocumentationProvider.java +++ b/platform/lang-api/src/com/intellij/lang/documentation/CodeDocumentationProvider.java @@ -16,7 +16,10 @@ package com.intellij.lang.documentation; +import com.intellij.openapi.util.Pair; import com.intellij.psi.PsiComment; +import com.intellij.psi.PsiElement; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** @@ -33,6 +36,28 @@ public interface CodeDocumentationProvider extends DocumentationProvider { @Nullable PsiComment findExistingDocComment(PsiComment contextElement); + /** + * Examines PSI hierarchy identified by the given 'start' element trying to find element which can be documented + * and it's doc comment (if any). + *

+ * Example: + *

+   *   int test() {
+   *     return [caret] 1;
+   *   }
+   * 
+ * PSI element at the caret (return element) is an entry point. This method is expected to return PSI method element + * and 'null' as the existing doc comment then. + * + * @param startPoint start traversal point + * @return comment anchor which is a given element or its anchor if the one is found and its doc comment (if existing). + * This method may return 'null' as an indication that no doc comment anchor and existing comment + * is available; + * returned pair must have non-null PSI element and nullable existing comment references then + */ + @Nullable + Pair parseContext(@NotNull PsiElement startPoint); + /** * Generate documentation comment content for given context. * @param contextComment context psi comment diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/FixDocCommentAction.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/FixDocCommentAction.java new file mode 100644 index 000000000000..353ea0cbe38a --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/FixDocCommentAction.java @@ -0,0 +1,253 @@ +/* + * Copyright 2000-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.codeInsight.editorActions; + +import com.intellij.lang.*; +import com.intellij.lang.documentation.CodeDocumentationProvider; +import com.intellij.lang.documentation.CompositeDocumentationProvider; +import com.intellij.lang.documentation.DocumentationProvider; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.editor.CaretModel; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.actionSystem.EditorAction; +import com.intellij.openapi.editor.actionSystem.EditorActionHandler; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.CodeStyleManager; +import com.intellij.psi.util.PsiUtilBase; +import com.intellij.util.text.CharArrayUtil; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +/** + * Creates documentation comment for the current context if it's not created yet (e.g. the caret is inside a method which + * doesn't have a doc comment). + *

+ * Updates existing documentation comment if necessary if the one exists. E.g. we've changed method signature and want to remove all + * outdated parameters and create stubs for the new ones. + * + * @author Denis Zhdanov + * @since 9/20/12 10:15 AM + */ +public class FixDocCommentAction extends EditorAction { + + @NotNull @NonNls public static final String ACTION_ID = "FixDocComment"; + + public FixDocCommentAction() { + super(new MyHandler()); + } + + private static final class MyHandler extends EditorActionHandler { + @Override + public void execute(Editor editor, DataContext dataContext) { + Project project = PlatformDataKeys.PROJECT.getData(dataContext); + if (project == null) { + return; + } + + PsiFile psiFile = LangDataKeys.PSI_FILE.getData(dataContext); + if (psiFile == null) { + return; + } + + process(psiFile, editor, project, editor.getCaretModel().getOffset()); + } + } + + private static void process(@NotNull final PsiFile file, @NotNull final Editor editor, @NotNull final Project project, int offset) { + PsiElement elementAtOffset = file.findElementAt(offset); + if (elementAtOffset == null) { + return; + } + + Language language = PsiUtilBase.getLanguageAtOffset(file, offset); + final CodeDocumentationProvider docProvider; + final DocumentationProvider langDocumentationProvider = LanguageDocumentation.INSTANCE.forLanguage(language); + if (langDocumentationProvider instanceof CompositeDocumentationProvider) { + docProvider = ((CompositeDocumentationProvider)langDocumentationProvider).getFirstCodeDocumentationProvider(); + } + else if (langDocumentationProvider instanceof CodeDocumentationProvider) { + docProvider = (CodeDocumentationProvider)langDocumentationProvider; + } + else { + docProvider = null; + } + if (docProvider == null) { + return; + } + + final Pair pair = docProvider.parseContext(elementAtOffset); + if (pair == null) { + return; + } + + Commenter c = LanguageCommenters.INSTANCE.forLanguage(language); + if (!(c instanceof CodeDocumentationAwareCommenter)) { + return; + } + final CodeDocumentationAwareCommenter commenter = (CodeDocumentationAwareCommenter)c; + final Runnable command = new Runnable() { + @Override + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + if (pair.second == null || pair.second.getTextRange().isEmpty()) { + generateComment(pair.first, editor, docProvider, commenter, project); + } + else { + fixCommentIfNecessary(pair.second); + } + } + }); + } + }; + CommandProcessor.getInstance().executeCommand(project, command, "Fix documentation", null); + + } + + /** + * Generates a comment if possible. + *

+ * It's assumed that this method {@link PsiDocumentManager#commitDocument(Document) syncs} all PSI-document + * changes during the processing. + * + * @param anchor target element for which a comment should be generated + * @param editor target editor + * @param commenter commenter to use + * @param project current project + */ + private static void generateComment(@NotNull PsiElement anchor, + @NotNull Editor editor, + @NotNull CodeDocumentationProvider documentationProvider, + @NotNull CodeDocumentationAwareCommenter commenter, + @NotNull Project project) + { + Document document = editor.getDocument(); + int commentStartOffset = anchor.getTextRange().getStartOffset(); + int lineStartOffset = document.getLineStartOffset(document.getLineNumber(commentStartOffset)); + if (lineStartOffset > 0 && lineStartOffset < commentStartOffset) { + // Example: + // void test1() { + // } + // void test2() { + // + // } + // We want to insert the comment at the start of the line where 'test2()' is declared. + int nonWhiteSpaceOffset = CharArrayUtil.shiftBackward(document.getCharsSequence(), commentStartOffset - 1, " \t"); + commentStartOffset = Math.max(nonWhiteSpaceOffset, lineStartOffset); + } + + int commentBodyRelativeOffset = 0; + int caretOffsetToSet = -1; + StringBuilder buffer = new StringBuilder(); + String commentPrefix = commenter.getDocumentationCommentPrefix(); + if (commentPrefix != null) { + buffer.append(commentPrefix).append("\n"); + commentBodyRelativeOffset += commentPrefix.length() + 1; + } + + String linePrefix = commenter.getDocumentationCommentLinePrefix(); + if (linePrefix != null) { + buffer.append(linePrefix); + commentBodyRelativeOffset += linePrefix.length(); + caretOffsetToSet = commentStartOffset + commentBodyRelativeOffset; + } + buffer.append("\n"); + commentBodyRelativeOffset++; + + String commentSuffix = commenter.getDocumentationCommentSuffix(); + if (commentSuffix != null) { + buffer.append(commentSuffix).append("\n"); + } + + if (buffer.length() <= 0) { + return; + } + + document.insertString(commentStartOffset, buffer); + PsiDocumentManager docManager = PsiDocumentManager.getInstance(project); + docManager.commitDocument(document); + + Pair pair = documentationProvider.parseContext(anchor); + if (pair == null || pair.second == null) { + return; + } + + String stub = documentationProvider.generateDocumentationContentStub(pair.second); + CaretModel caretModel = editor.getCaretModel(); + if (stub != null) { + int insertionOffset = commentStartOffset + commentBodyRelativeOffset; + //if (CodeStyleSettingsManager.getSettings(project).JD_ADD_BLANK_AFTER_DESCRIPTION) { + // buffer.setLength(0); + // if (linePrefix != null) { + // buffer.append(linePrefix); + // } + // buffer.append("\n"); + // buffer.append(stub); + // stub = buffer.toString(); + //} + document.insertString(insertionOffset, stub); + docManager.commitDocument(document); + pair = documentationProvider.parseContext(anchor); + } + + if (caretOffsetToSet >= 0) { + caretModel.moveToOffset(caretOffsetToSet); + } + + if (pair == null || pair.second == null) { + return; + } + + int start = Math.min(calcStartReformatOffset(pair.first), calcStartReformatOffset(pair.second)); + int end = pair.second.getTextRange().getEndOffset(); + + CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project); + codeStyleManager.reformatText(anchor.getContainingFile(), start, end); + + int caretOffset = caretModel.getOffset(); + if (caretOffset > 0 && caretOffset <= document.getTextLength()) { + char c = document.getCharsSequence().charAt(caretOffset - 1); + if (!StringUtil.isWhiteSpace(c)) { + document.insertString(caretOffset, " "); + caretModel.moveToOffset(caretOffset + 1); + } + } + } + + private static int calcStartReformatOffset(@NotNull PsiElement element) { + int result = element.getTextRange().getStartOffset(); + for (PsiElement e = element.getPrevSibling(); e != null; e = e.getPrevSibling()) { + if (e instanceof PsiWhiteSpace) { + result = e.getTextRange().getStartOffset(); + } + else { + break; + } + } + return result; + } + + private static void fixCommentIfNecessary(@NotNull PsiComment docComment) { + // TODO den implement + } +} diff --git a/platform/platform-resources-en/src/messages/ActionsBundle.properties b/platform/platform-resources-en/src/messages/ActionsBundle.properties index 1277f91a6d86..e2a86d93bfa3 100644 --- a/platform/platform-resources-en/src/messages/ActionsBundle.properties +++ b/platform/platform-resources-en/src/messages/ActionsBundle.properties @@ -344,6 +344,8 @@ action.ShowFilePath.text=File _Path action.ShowFilePath.description=Popup path of the current file for opening in external file manager action.SelectIn.text=Se_lect In... action.SelectIn.description=Select the current class or method in any view +action.FixDocComment.text=Fix doc comment +action.FixDocComment.description=Generate or fix doc comment for the current context action.QuickJavaDoc.text=Quick _Documentation action.QuickJavaDoc.description=Show a popup window with documentation for the symbol at caret action.QuickImplementations.text=Quic_k Definition diff --git a/platform/platform-resources/src/idea/LangActions.xml b/platform/platform-resources/src/idea/LangActions.xml index f7b95ad65248..b71dc88deb6f 100644 --- a/platform/platform-resources/src/idea/LangActions.xml +++ b/platform/platform-resources/src/idea/LangActions.xml @@ -158,6 +158,8 @@ + + diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/documentation/GroovyDocumentationProvider.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/documentation/GroovyDocumentationProvider.java index b3e341508e3a..31a5d70dcefe 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/documentation/GroovyDocumentationProvider.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/documentation/GroovyDocumentationProvider.java @@ -25,6 +25,7 @@ import com.intellij.lang.documentation.CompositeDocumentationProvider; import com.intellij.lang.documentation.ExternalDocumentationProvider; import com.intellij.lang.java.JavaDocumentationProvider; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; import com.intellij.psi.impl.source.javadoc.PsiDocParamRef; @@ -37,6 +38,7 @@ import com.intellij.util.StringBuilderSpinAllocator; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashMap; import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.dsl.CustomMembersGenerator; import org.jetbrains.plugins.groovy.dsl.holders.NonCodeMembersHolder; @@ -459,6 +461,17 @@ public class GroovyDocumentationProvider implements CodeDocumentationProvider, E return null; } + @Nullable + @Override + public Pair parseContext(@NotNull PsiElement startPoint) { + for (PsiElement e = startPoint; e != null; e = e.getParent()) { + if (e instanceof GrDocCommentOwner) { + return Pair.create(e, ((GrDocCommentOwner)e).getDocComment()); + } + } + return null; + } + public String generateDocumentationContentStub(PsiComment contextComment) { if (!(contextComment instanceof GrDocComment)) { return null; From e32ff337d52330d8ec6ef4f2d8156bb5a347a0f1 Mon Sep 17 00:00:00 2001 From: peter Date: Thu, 20 Sep 2012 18:44:08 +0400 Subject: [PATCH 11/12] avoid vetoing document save forever in case of too quick commit --- .../SaveCommittingDocumentsVetoer.java | 2 +- .../VetoSavingCommittingDocumentsAdapter.java | 47 ++++++++++++------- 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/SaveCommittingDocumentsVetoer.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/SaveCommittingDocumentsVetoer.java index 6dd9d5b4e531..3ca4be56ebcd 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/SaveCommittingDocumentsVetoer.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/SaveCommittingDocumentsVetoer.java @@ -40,7 +40,7 @@ public class SaveCommittingDocumentsVetoer implements FileDocumentSynchronizatio return false; } if (beingCommitted instanceof Project) { - boolean allowSave = myAdapter.showAllowSaveDialog((Project) beingCommitted, Collections.singletonList(document)); + boolean allowSave = myAdapter.showAllowSaveDialog(Collections.singletonMap(document, (Project)beingCommitted)); if (!allowSave) { return false; } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/VetoSavingCommittingDocumentsAdapter.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/VetoSavingCommittingDocumentsAdapter.java index 04efd6e773e1..449b0e0f077b 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/VetoSavingCommittingDocumentsAdapter.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/VetoSavingCommittingDocumentsAdapter.java @@ -30,14 +30,15 @@ import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.FileDocumentManagerAdapter; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.UserDataHolderEx; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vcs.changes.ui.CommitHelper; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; -import java.util.List; +import java.util.Map; public class VetoSavingCommittingDocumentsAdapter implements ApplicationComponent { static final Object SAVE_DENIED = new Object(); @@ -57,39 +58,49 @@ public class VetoSavingCommittingDocumentsAdapter implements ApplicationComponen ApplicationManager.getApplication().getMessageBus().connect().subscribe(AppTopics.FILE_DOCUMENT_SYNC, new FileDocumentManagerAdapter() { @Override public void beforeAllDocumentsSaving() { - List documentsToWarn = new ArrayList(); - final Document[] unsavedDocuments = myFileDocumentManager.getUnsavedDocuments(); - Project commitOwnerProject = null; - for (Document unsavedDocument : unsavedDocuments) { - final Object data = unsavedDocument.getUserData(CommitHelper.DOCUMENT_BEING_COMMITTED_KEY); - if (data instanceof Project) { - commitOwnerProject = (Project) data; - documentsToWarn.add(unsavedDocument); - } - } + Map documentsToWarn = getDocumentsBeingCommitted(); if (!documentsToWarn.isEmpty()) { - boolean allowSave = showAllowSaveDialog(commitOwnerProject, documentsToWarn); - for (Document document : documentsToWarn) { - document.putUserData(CommitHelper.DOCUMENT_BEING_COMMITTED_KEY, allowSave ? null : SAVE_DENIED); - } + boolean allowSave = showAllowSaveDialog(documentsToWarn); + updateSaveability(documentsToWarn, allowSave); } } }); } + private Map getDocumentsBeingCommitted() { + Map documentsToWarn = ContainerUtil.newHashMap(); + for (Document unsavedDocument : myFileDocumentManager.getUnsavedDocuments()) { + final Object data = unsavedDocument.getUserData(CommitHelper.DOCUMENT_BEING_COMMITTED_KEY); + if (data instanceof Project) { + documentsToWarn.put(unsavedDocument, (Project)data); + } + } + return documentsToWarn; + } + + private static void updateSaveability(Map documentsToWarn, boolean allowSave) { + Object newValue = allowSave ? null : SAVE_DENIED; + for (Document document : documentsToWarn.keySet()) { + Project oldData = documentsToWarn.get(document); + //the committing thread could have finished already and file is not being committed anymore + ((UserDataHolderEx)document).replace(CommitHelper.DOCUMENT_BEING_COMMITTED_KEY, oldData, newValue); + } + } + public void disposeComponent() { } - boolean showAllowSaveDialog(Project project, List documentsToWarn) { + boolean showAllowSaveDialog(Map documentsToWarn) { StringBuilder messageBuilder = new StringBuilder("The following " + (documentsToWarn.size() == 1 ? "file is" : "files are") + " currently being committed to the VCS. " + "Saving now could cause inconsistent data to be committed.\n"); - for (Document document : documentsToWarn) { + for (Document document : documentsToWarn.keySet()) { final VirtualFile file = myFileDocumentManager.getFile(document); messageBuilder.append(FileUtil.toSystemDependentName(file.getPath())).append("\n"); } messageBuilder.append("Save the ").append(documentsToWarn.size() == 1 ? "file" : "files").append(" now?"); + Project project = documentsToWarn.values().iterator().next(); int rc = Messages.showOkCancelDialog(project, messageBuilder.toString(), "Save Files During Commit", "Save Now", "Postpone Save", Messages.getQuestionIcon()); return rc == 0; From fa6cd4622f5f93508e328324b1616bd6181d03e2 Mon Sep 17 00:00:00 2001 From: Eugene Zhuravlev Date: Thu, 20 Sep 2012 17:46:05 +0400 Subject: [PATCH 12/12] tag messages with sessionId --- .../com/intellij/compiler/ProblemsView.java | 33 +++++++++++-------- .../compiler/impl/CompileContextImpl.java | 3 +- .../intellij/compiler/impl/CompileDriver.java | 8 ++--- .../compiler/impl/ProblemsViewImpl.java | 23 +++---------- .../server/AutoMakeMessageHandler.java | 10 +++--- .../compiler/server/BuildManager.java | 16 ++++----- .../server/BuildMessageDispatcher.java | 4 +-- .../server/BuilderMessageHandler.java | 4 +-- .../server/DefaultMessageHandler.java | 10 +++--- .../errorTreeView/NewErrorTreeViewPanel.java | 18 ++++++---- 10 files changed, 63 insertions(+), 66 deletions(-) diff --git a/java/compiler/impl/src/com/intellij/compiler/ProblemsView.java b/java/compiler/impl/src/com/intellij/compiler/ProblemsView.java index 2221fde1e16b..63d055e55e44 100644 --- a/java/compiler/impl/src/com/intellij/compiler/ProblemsView.java +++ b/java/compiler/impl/src/com/intellij/compiler/ProblemsView.java @@ -20,6 +20,7 @@ import com.intellij.openapi.compiler.CompileScope; import com.intellij.openapi.compiler.CompilerMessage; import com.intellij.openapi.compiler.CompilerMessageCategory; import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.fileEditor.OpenFileDescriptor; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.pom.Navigatable; @@ -30,6 +31,7 @@ import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; +import java.util.UUID; /** * @author Eugene Zhuravlev @@ -37,6 +39,8 @@ import java.util.StringTokenizer; */ public abstract class ProblemsView { + private final Project myProject; + public static class SERVICE { private SERVICE() { } @@ -46,29 +50,30 @@ public abstract class ProblemsView { } } + protected ProblemsView(Project project) { + myProject = project; + } + public abstract void clearMessages(CompileScope scope); public abstract void clearMessages(); - public abstract void addMessage(int type, @NotNull String[] text, @Nullable VirtualFile file, int line, int column, @Nullable Object data); + public abstract void addMessage(int type, @NotNull String[] text, @Nullable String groupName, @NotNull Navigatable navigatable, @Nullable String exportTextPrefix, @Nullable String rendererTextPrefix, @NotNull UUID sessionId); - public abstract void addMessage(int type, @NotNull String[] text, @Nullable VirtualFile underFileGroup, @Nullable VirtualFile file, int line, int column, @Nullable Object data); - - public abstract void addMessage(int type, @NotNull String[] text, @Nullable String groupName, @NotNull Navigatable navigatable, @Nullable String exportTextPrefix, @Nullable String rendererTextPrefix, @Nullable Object data); - - public void addMessage(CompilerMessage message) { - final Navigatable navigatable = message.getNavigatable(); + public void addMessage(CompilerMessage message, @NotNull UUID sessionId) { final VirtualFile file = message.getVirtualFile(); + Navigatable navigatable = message.getNavigatable(); + if (navigatable == null) { + if (file == null) { + return; // both navigatable and file must not be null + } + navigatable = new OpenFileDescriptor(myProject, file, -1, -1); + } final CompilerMessageCategory category = message.getCategory(); final int type = CompilerTask.translateCategory(category); final String[] text = convertMessage(message); - if (navigatable != null) { - final String groupName = file != null? file.getPresentableUrl() : category.getPresentableText(); - addMessage(type, text, groupName, navigatable, message.getExportTextPrefix(), message.getRenderTextPrefix(), message.getVirtualFile()); - } - else { - addMessage(type, text, file, -1, -1, message.getVirtualFile()); - } + final String groupName = file != null? file.getPresentableUrl() : category.getPresentableText(); + addMessage(type, text, groupName, navigatable, message.getExportTextPrefix(), message.getRenderTextPrefix(), sessionId); } public abstract void setProgress(String text, float fraction); diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/CompileContextImpl.java b/java/compiler/impl/src/com/intellij/compiler/impl/CompileContextImpl.java index 3db0e3677834..1768d34a2b63 100644 --- a/java/compiler/impl/src/com/intellij/compiler/impl/CompileContextImpl.java +++ b/java/compiler/impl/src/com/intellij/compiler/impl/CompileContextImpl.java @@ -81,6 +81,7 @@ public class CompileContextImpl extends UserDataHolderBase implements CompileCon private final ProjectFileIndex myProjectFileIndex; // cached for performance reasons private final ProjectCompileScope myProjectCompileScope; private final long myStartCompilationStamp; + private final UUID mySessionId = UUID.randomUUID(); public CompileContextImpl(final Project project, final CompilerTask compilerSession, @@ -254,7 +255,7 @@ public class CompileContextImpl extends UserDataHolderBase implements CompileCon myTask.addMessage(msg); } if (myShouldUpdateProblemsView && msg.getCategory() == CompilerMessageCategory.ERROR) { - ProblemsViewImpl.SERVICE.getInstance(myProject).addMessage(msg); + ProblemsViewImpl.SERVICE.getInstance(myProject).addMessage(msg, mySessionId); } } diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java b/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java index 051ff00159c5..daa36a0445fd 100644 --- a/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java +++ b/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java @@ -444,14 +444,14 @@ public class CompileDriver { buildManager.cancelAutoMakeTasks(myProject); return buildManager.scheduleBuild(myProject, compileContext.isRebuild(), compileContext.isMake(), scopes, paths, builderParams, new DefaultMessageHandler(myProject) { @Override - public void buildStarted() { + public void buildStarted(UUID sessionId) { final ProblemsView view = ProblemsViewImpl.SERVICE.getInstance(myProject); view.clearMessages(compileContext.getCompileScope()); view.clearProgress(); } @Override - public void sessionTerminated() { + public void sessionTerminated(UUID sessionId) { } @Override @@ -466,7 +466,7 @@ public class CompileDriver { } @Override - protected void handleCompileMessage(CmdlineRemoteProto.Message.BuilderMessage.CompileMessage message) { + protected void handleCompileMessage(UUID sessionId, CmdlineRemoteProto.Message.BuilderMessage.CompileMessage message) { final CmdlineRemoteProto.Message.BuilderMessage.CompileMessage.Kind kind = message.getKind(); //System.out.println(compilerMessage.getText()); if (kind == CmdlineRemoteProto.Message.BuilderMessage.CompileMessage.Kind.PROGRESS) { @@ -494,7 +494,7 @@ public class CompileDriver { } @Override - protected void handleBuildEvent(CmdlineRemoteProto.Message.BuilderMessage.BuildEvent event) { + protected void handleBuildEvent(UUID sessionId, CmdlineRemoteProto.Message.BuilderMessage.BuildEvent event) { final CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.Type eventType = event.getEventType(); switch (eventType) { case FILES_GENERATED: diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/ProblemsViewImpl.java b/java/compiler/impl/src/com/intellij/compiler/impl/ProblemsViewImpl.java index 3067959c64f3..0e20e9e450b0 100644 --- a/java/compiler/impl/src/com/intellij/compiler/impl/ProblemsViewImpl.java +++ b/java/compiler/impl/src/com/intellij/compiler/impl/ProblemsViewImpl.java @@ -20,7 +20,6 @@ import com.intellij.openapi.Disposable; import com.intellij.openapi.compiler.CompileScope; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.ToolWindow; import com.intellij.openapi.wm.ToolWindowAnchor; import com.intellij.openapi.wm.ToolWindowManager; @@ -31,6 +30,8 @@ import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.UUID; + /** * @author Eugene Zhuravlev * Date: 9/18/12 @@ -41,6 +42,7 @@ public class ProblemsViewImpl extends ProblemsView{ private final ProblemsViewPanel myPanel; public ProblemsViewImpl(final Project project, final ToolWindowManager wm) { + super(project); myPanel = new ProblemsViewPanel(project); Disposer.register(project, new Disposable() { @Override @@ -86,28 +88,13 @@ public class ProblemsViewImpl extends ProblemsView{ myPanel.clearMessages(); } - @Override - public void addMessage(final int type, @NotNull final String[] text, @Nullable final VirtualFile file, final int line, final int column, @Nullable final Object data) { - myPanel.addMessage(type, text, file, line, column, data); - } - - @Override - public void addMessage(final int type, - @NotNull final String[] text, - @Nullable final VirtualFile underFileGroup, - @Nullable final VirtualFile file, - final int line, - final int column, @Nullable final Object data) { - myPanel.addMessage(type, text, underFileGroup, file, line, column, data); - } - @Override public void addMessage(final int type, @NotNull final String[] text, @Nullable final String groupName, @NotNull final Navigatable navigatable, - @Nullable final String exportTextPrefix, @Nullable final String rendererTextPrefix, @Nullable final Object data) { - myPanel.addMessage(type, text, groupName, navigatable, exportTextPrefix, rendererTextPrefix, data); + @Nullable final String exportTextPrefix, @Nullable final String rendererTextPrefix, @Nullable final UUID sessionId) { + myPanel.addMessage(type, text, groupName, navigatable, exportTextPrefix, rendererTextPrefix, sessionId); } @Override diff --git a/java/compiler/impl/src/com/intellij/compiler/server/AutoMakeMessageHandler.java b/java/compiler/impl/src/com/intellij/compiler/server/AutoMakeMessageHandler.java index f816ff06442b..ec1bb548e823 100644 --- a/java/compiler/impl/src/com/intellij/compiler/server/AutoMakeMessageHandler.java +++ b/java/compiler/impl/src/com/intellij/compiler/server/AutoMakeMessageHandler.java @@ -53,14 +53,14 @@ class AutoMakeMessageHandler extends DefaultMessageHandler { } @Override - public void buildStarted() { + public void buildStarted(UUID sessionId) { final ProblemsView view = ProblemsView.SERVICE.getInstance(myProject); view.clearMessages(); view.clearProgress(); } @Override - protected void handleBuildEvent(CmdlineRemoteProto.Message.BuilderMessage.BuildEvent event) { + protected void handleBuildEvent(UUID sessionId, CmdlineRemoteProto.Message.BuilderMessage.BuildEvent event) { if (myProject.isDisposed()) { return; } @@ -86,7 +86,7 @@ class AutoMakeMessageHandler extends DefaultMessageHandler { } @Override - protected void handleCompileMessage(CmdlineRemoteProto.Message.BuilderMessage.CompileMessage message) { + protected void handleCompileMessage(final UUID sessionId, CmdlineRemoteProto.Message.BuilderMessage.CompileMessage message) { if (myProject.isDisposed()) { return; } @@ -107,7 +107,7 @@ class AutoMakeMessageHandler extends DefaultMessageHandler { final VirtualFile vFile = sourceFilePath != null? LocalFileSystem.getInstance().findFileByPath(FileUtil.toSystemIndependentName(sourceFilePath)) : null; final long line = message.hasLine() ? message.getLine() : -1; final long column = message.hasColumn() ? message.getColumn() : -1; - ProblemsView.SERVICE.getInstance(myProject).addMessage(new CompilerMessageImpl(myProject, CompilerMessageCategory.ERROR, message.getText(), vFile, (int)line, (int)column, null)); + ProblemsView.SERVICE.getInstance(myProject).addMessage(new CompilerMessageImpl(myProject, CompilerMessageCategory.ERROR, message.getText(), vFile, (int)line, (int)column, null), sessionId); } } @@ -117,7 +117,7 @@ class AutoMakeMessageHandler extends DefaultMessageHandler { } @Override - public void sessionTerminated() { + public void sessionTerminated(UUID sessionId) { String statusMessage = null/*"Auto make completed"*/; switch (myBuildStatus) { case SUCCESS: diff --git a/java/compiler/impl/src/com/intellij/compiler/server/BuildManager.java b/java/compiler/impl/src/com/intellij/compiler/server/BuildManager.java index 4eb81cefe9b6..dd6d4a4b2a8f 100644 --- a/java/compiler/impl/src/com/intellij/compiler/server/BuildManager.java +++ b/java/compiler/impl/src/com/intellij/compiler/server/BuildManager.java @@ -197,7 +197,7 @@ public class BuildManager implements ApplicationComponent{ return false; } } - + // todo: probably we do not need this excessive filtering for (Project project : activeProjects) { if (!project.isInitialized() || ProjectRootManager.getInstance(project).getFileIndex().isInContent(eventFile)) { return true; @@ -451,7 +451,7 @@ public class BuildManager implements ApplicationComponent{ } catch (Exception e) { handler.handleFailure(sessionId, CmdlineProtoUtil.createFailure(e.getMessage(), null)); - handler.sessionTerminated(); + handler.sessionTerminated(sessionId); return null; } } @@ -469,7 +469,7 @@ public class BuildManager implements ApplicationComponent{ @Override public void run() { if (future.isCancelled() || project.isDisposed()) { - handler.sessionTerminated(); + handler.sessionTerminated(sessionId); future.setDone(); return; } @@ -516,7 +516,7 @@ public class BuildManager implements ApplicationComponent{ try { if (project.isDisposed()) { myMessageDispatcher.unregisterBuildMessageHandler(sessionId); - handler.sessionTerminated(); + handler.sessionTerminated(sessionId); return; } myBuildsInProgress.put(projectPath, future); @@ -533,7 +533,7 @@ public class BuildManager implements ApplicationComponent{ public void processTerminated(ProcessEvent event) { final BuilderMessageHandler handler = myMessageDispatcher.unregisterBuildMessageHandler(sessionId); if (handler != null) { - handler.sessionTerminated(); + handler.sessionTerminated(sessionId); } } @@ -572,7 +572,7 @@ public class BuildManager implements ApplicationComponent{ catch (ExecutionException e) { myMessageDispatcher.unregisterBuildMessageHandler(sessionId); handler.handleFailure(sessionId, CmdlineProtoUtil.createFailure(e.getMessage(), e)); - handler.sessionTerminated(); + handler.sessionTerminated(sessionId); } finally { myBuildsInProgress.remove(projectPath); @@ -584,7 +584,7 @@ public class BuildManager implements ApplicationComponent{ catch (Throwable e) { myMessageDispatcher.unregisterBuildMessageHandler(sessionId); handler.handleFailure(sessionId, CmdlineProtoUtil.createFailure(e.getMessage(), e)); - handler.sessionTerminated(); + handler.sessionTerminated(sessionId); future.setDone(); } } @@ -594,7 +594,7 @@ public class BuildManager implements ApplicationComponent{ } catch (Throwable e) { handler.handleFailure(sessionId, CmdlineProtoUtil.createFailure(e.getMessage(), e)); - handler.sessionTerminated(); + handler.sessionTerminated(sessionId); } return null; diff --git a/java/compiler/impl/src/com/intellij/compiler/server/BuildMessageDispatcher.java b/java/compiler/impl/src/com/intellij/compiler/server/BuildMessageDispatcher.java index 18e21c51d99c..5b089127a738 100644 --- a/java/compiler/impl/src/com/intellij/compiler/server/BuildMessageDispatcher.java +++ b/java/compiler/impl/src/com/intellij/compiler/server/BuildMessageDispatcher.java @@ -116,7 +116,7 @@ class BuildMessageDispatcher extends SimpleChannelHandler { if (msgType == CmdlineRemoteProto.Message.BuilderMessage.Type.PARAM_REQUEST) { final CmdlineRemoteProto.Message.ControllerMessage params = sessionData.params; if (params != null) { - handler.buildStarted(); + handler.buildStarted(sessionId); sessionData.params = null; Channels.write(ctx.getChannel(), CmdlineProtoUtil.toMessage(sessionId, params)); } @@ -146,7 +146,7 @@ class BuildMessageDispatcher extends SimpleChannelHandler { final BuilderMessageHandler handler = unregisterBuildMessageHandler(sessionData.sessionId); if (handler != null) { // notify the handler only if it has not been notified yet - handler.sessionTerminated(); + handler.sessionTerminated(sessionData.sessionId); } } } diff --git a/java/compiler/impl/src/com/intellij/compiler/server/BuilderMessageHandler.java b/java/compiler/impl/src/com/intellij/compiler/server/BuilderMessageHandler.java index e3711efd1532..fadf63b893ed 100644 --- a/java/compiler/impl/src/com/intellij/compiler/server/BuilderMessageHandler.java +++ b/java/compiler/impl/src/com/intellij/compiler/server/BuilderMessageHandler.java @@ -25,11 +25,11 @@ import java.util.UUID; * Date: 4/25/12 */ public interface BuilderMessageHandler { - void buildStarted(); + void buildStarted(UUID sessionId); void handleBuildMessage(Channel channel, UUID sessionId, CmdlineRemoteProto.Message.BuilderMessage msg); void handleFailure(UUID sessionId, CmdlineRemoteProto.Message.Failure failure); - void sessionTerminated(); + void sessionTerminated(UUID sessionId); } diff --git a/java/compiler/impl/src/com/intellij/compiler/server/DefaultMessageHandler.java b/java/compiler/impl/src/com/intellij/compiler/server/DefaultMessageHandler.java index 01b6bc7e3bc0..add20bda9b65 100644 --- a/java/compiler/impl/src/com/intellij/compiler/server/DefaultMessageHandler.java +++ b/java/compiler/impl/src/com/intellij/compiler/server/DefaultMessageHandler.java @@ -63,17 +63,17 @@ public abstract class DefaultMessageHandler implements BuilderMessageHandler { } @Override - public void buildStarted() { + public void buildStarted(UUID sessionId) { } @Override public final void handleBuildMessage(final Channel channel, final UUID sessionId, final CmdlineRemoteProto.Message.BuilderMessage msg) { switch (msg.getType()) { case BUILD_EVENT: - handleBuildEvent(msg.getBuildEvent()); + handleBuildEvent(sessionId, msg.getBuildEvent()); break; case COMPILE_MESSAGE: - handleCompileMessage(msg.getCompileMessage()); + handleCompileMessage(sessionId, msg.getCompileMessage()); break; case CONSTANT_SEARCH_TASK: final CmdlineRemoteProto.Message.BuilderMessage.ConstantSearchTask task = msg.getConstantSearchTask(); @@ -87,9 +87,9 @@ public abstract class DefaultMessageHandler implements BuilderMessageHandler { } } - protected abstract void handleCompileMessage(CmdlineRemoteProto.Message.BuilderMessage.CompileMessage message); + protected abstract void handleCompileMessage(UUID sessionId, CmdlineRemoteProto.Message.BuilderMessage.CompileMessage message); - protected abstract void handleBuildEvent(CmdlineRemoteProto.Message.BuilderMessage.BuildEvent event); + protected abstract void handleBuildEvent(UUID sessionId, CmdlineRemoteProto.Message.BuilderMessage.BuildEvent event); private void handleConstantSearchTask(Channel channel, UUID sessionId, CmdlineRemoteProto.Message.BuilderMessage.ConstantSearchTask task) { final String ownerClassName = task.getOwnerClassName(); diff --git a/platform/platform-impl/src/com/intellij/ide/errorTreeView/NewErrorTreeViewPanel.java b/platform/platform-impl/src/com/intellij/ide/errorTreeView/NewErrorTreeViewPanel.java index fa0efbab5a1b..95db8f41bada 100644 --- a/platform/platform-impl/src/com/intellij/ide/errorTreeView/NewErrorTreeViewPanel.java +++ b/platform/platform-impl/src/com/intellij/ide/errorTreeView/NewErrorTreeViewPanel.java @@ -23,6 +23,7 @@ import com.intellij.ide.errorTreeView.impl.ErrorViewTextExporter; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.fileEditor.OpenFileDescriptor; import com.intellij.openapi.ide.CopyPasteManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Disposer; @@ -263,8 +264,7 @@ public class NewErrorTreeViewPanel extends JPanel implements DataProvider, Occur int line, int column, @Nullable Object data) { - myErrorViewStructure - .addMessage(ErrorTreeElementKind.convertMessageFromCompilerErrorType(type), text, underFileGroup, file, line, column, data); + myErrorViewStructure.addMessage(ErrorTreeElementKind.convertMessageFromCompilerErrorType(type), text, underFileGroup, file, line, column, data); myBuilder.updateTree(); } @@ -275,11 +275,15 @@ public class NewErrorTreeViewPanel extends JPanel implements DataProvider, Occur @Nullable String exportTextPrefix, @Nullable String rendererTextPrefix, @Nullable Object data) { - myErrorViewStructure.addNavigatableMessage(groupName, navigatable, ErrorTreeElementKind.convertMessageFromCompilerErrorType(type), text, - data, - exportTextPrefix == null ? "" : exportTextPrefix, - rendererTextPrefix == null ? "" : rendererTextPrefix, - data instanceof VirtualFile ? (VirtualFile)data : null); + + VirtualFile file = data instanceof VirtualFile ? (VirtualFile)data : null; + if (file == null && navigatable instanceof OpenFileDescriptor) { + file = ((OpenFileDescriptor)navigatable).getFile(); + } + final String exportPrefix = exportTextPrefix == null ? "" : exportTextPrefix; + final String renderPrefix = rendererTextPrefix == null ? "" : rendererTextPrefix; + final ErrorTreeElementKind kind = ErrorTreeElementKind.convertMessageFromCompilerErrorType(type); + myErrorViewStructure.addNavigatableMessage(groupName, navigatable, kind, text, data, exportPrefix, renderPrefix, file); myBuilder.updateTree(); }