diff --git a/plugins/ant/src/com/intellij/lang/ant/dom/AntDomMacrodefAttributeReference.java b/plugins/ant/src/com/intellij/lang/ant/dom/AntDomMacrodefAttributeReference.java new file mode 100644 index 000000000000..a5da17a34084 --- /dev/null +++ b/plugins/ant/src/com/intellij/lang/ant/dom/AntDomMacrodefAttributeReference.java @@ -0,0 +1,114 @@ +/* + * Copyright 2000-2010 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.lang.ant.dom; + +import com.intellij.codeInsight.lookup.AutoCompletionPolicy; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.lookup.LookupElementBuilder; +import com.intellij.lang.ant.AntBundle; +import com.intellij.openapi.util.TextRange; +import com.intellij.pom.references.PomService; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiReference; +import com.intellij.psi.impl.PsiManagerEx; +import com.intellij.psi.impl.source.resolve.ResolveCache; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.xml.DomElement; +import com.intellij.util.xml.DomTarget; +import com.intellij.util.xml.DomUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Eugene Zhuravlev + * Date: Aug 16, 2010 + */ +public class AntDomMacrodefAttributeReference extends AntDomReferenceBase{ + + public AntDomMacrodefAttributeReference(PsiElement element, TextRange range) { + super(element, range, true); + } + + public String getUnresolvedMessagePattern() { + return AntBundle.message("unknown.macro.attribute", getCanonicalText()); + } + + public PsiElement resolve() { + return ((PsiManagerEx)getElement().getManager()).getResolveCache().resolveWithCaching(this, MyResolver.INSTANCE, false, false); + } + + @NotNull + public Object[] getVariants() { + final AntDomMacroDef parentMacrodef = getParentMacrodef(); + if (parentMacrodef != null) { + final List variants = new ArrayList(); + for (AntDomMacrodefAttribute attribute : parentMacrodef.getMacroAttributes()) { + final String attribName = attribute.getName().getStringValue(); + if (attribName != null && attribName.length() > 0) { + final LookupElementBuilder builder = LookupElementBuilder.create(attribName); + final LookupElement element = AutoCompletionPolicy.GIVE_CHANCE_TO_OVERWRITE.applyPolicy(builder); + variants.add(element); + } + } + return ContainerUtil.toArray(variants, new Object[variants.size()]); + } + return EMPTY_ARRAY; + } + + @Nullable + private AntDomMacroDef getParentMacrodef() { + final PsiElement element = getElement(); + if (element == null) { + return null; + } + final DomElement domElement = DomUtil.getDomElement(element); + if (domElement == null) { + return null; + } + return domElement.getParentOfType(AntDomMacroDef.class, false); + } + + private static class MyResolver implements ResolveCache.Resolver { + + static final MyResolver INSTANCE = new MyResolver(); + + public PsiElement resolve(PsiReference psiReference, boolean incompleteCode) { + final PsiElement element = psiReference.getElement(); + if (element == null) { + return null; + } + final DomElement domElement = DomUtil.getDomElement(element); + if (domElement == null) { + return null; + } + final AntDomMacroDef macrodef = domElement.getParentOfType(AntDomMacroDef.class, false); + if (macrodef == null) { + return null; + } + final String name = AntStringResolver.computeString(domElement, psiReference.getCanonicalText()); + for (AntDomMacrodefAttribute attribute : macrodef.getMacroAttributes()) { + if (name.equals(attribute.getName().getStringValue())) { + final DomTarget target = DomTarget.getTarget(attribute); + return target != null? PomService.convertToPsi(target) : null; + } + } + return null; + } + } +} diff --git a/plugins/ant/src/com/intellij/lang/ant/dom/AntReferenceInjector.java b/plugins/ant/src/com/intellij/lang/ant/dom/AntReferenceInjector.java index 5cf5b7e9ae16..58fa3a780e63 100644 --- a/plugins/ant/src/com/intellij/lang/ant/dom/AntReferenceInjector.java +++ b/plugins/ant/src/com/intellij/lang/ant/dom/AntReferenceInjector.java @@ -15,6 +15,7 @@ */ package com.intellij.lang.ant.dom; +import com.intellij.lang.ant.misc.PsiReferenceListSpinAllocator; import com.intellij.openapi.util.TextRange; import com.intellij.psi.ElementManipulators; import com.intellij.psi.PsiElement; @@ -22,13 +23,15 @@ import com.intellij.psi.PsiReference; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.xml.XmlAttribute; import com.intellij.psi.xml.XmlAttributeValue; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.xml.ConvertContext; import com.intellij.util.xml.DomElement; import com.intellij.util.xml.DomReferenceInjector; +import com.intellij.util.xml.DomUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; +import java.util.Collection; import java.util.List; /** @@ -48,14 +51,21 @@ class AntReferenceInjector implements DomReferenceInjector { @NotNull public PsiReference[] inject(@Nullable String unresolvedText, @NotNull PsiElement element, @NotNull ConvertContext context) { if (element instanceof XmlAttributeValue) { - final List refs = new ArrayList(); - addPropertyReferences(context, (XmlAttributeValue)element, refs); - return refs.toArray(new PsiReference[refs.size()]); + final XmlAttributeValue xmlAttributeValue = (XmlAttributeValue)element; + final List refs = PsiReferenceListSpinAllocator.alloc(); + try { + addPropertyReferences(context, xmlAttributeValue, refs); + addMacrodefParameterRefs(xmlAttributeValue, refs); + return refs.size() == 0? PsiReference.EMPTY_ARRAY : ContainerUtil.toArray(refs, new PsiReference[refs.size()]); + } + finally { + PsiReferenceListSpinAllocator.dispose(refs); + } } return PsiReference.EMPTY_ARRAY; } - private static void addPropertyReferences(@NotNull ConvertContext context, final XmlAttributeValue xmlAttributeValue, final List result) { + private static void addPropertyReferences(@NotNull ConvertContext context, final XmlAttributeValue xmlAttributeValue, final Collection result) { final String value = xmlAttributeValue.getValue(); final DomElement contextElement = context.getInvocationElement(); @@ -75,7 +85,7 @@ class AntReferenceInjector implements DomReferenceInjector { } } - if (xmlAttributeValue != null && value.indexOf("@{") < 0) { + if (xmlAttributeValue != null /*&& value.indexOf("@{") < 0*/) { final int valueBeginingOffset = Math.abs(xmlAttributeValue.getTextRange().getStartOffset() - xmlAttributeValue.getValueTextRange().getStartOffset()); int startIndex; int endIndex = -1; @@ -117,5 +127,45 @@ class AntReferenceInjector implements DomReferenceInjector { } } } + + public static void addMacrodefParameterRefs(@NotNull XmlAttributeValue element, final Collection refs) { + final DomElement domElement = DomUtil.getDomElement(element); + if (domElement == null) { + return; + } + final AntDomMacroDef macrodef = domElement.getParentOfType(AntDomMacroDef.class, true); + if (macrodef == null) { + return; + } + final String text = ElementManipulators.getValueText(element); + final int valueBeginingOffset = Math.abs(element.getTextRange().getStartOffset() - element.getValueTextRange().getStartOffset()); + int startIndex; + int endIndex = -1; + while ((startIndex = text.indexOf("@{", endIndex + 1)) > endIndex) { + startIndex += 2; + endIndex = startIndex; + int nestedBrackets = 0; + while (text.length() > endIndex) { + final char ch = text.charAt(endIndex); + if (ch == '}') { + if (nestedBrackets == 0) { + break; + } + --nestedBrackets; + } + else if (ch == '{') { + ++nestedBrackets; + } + ++endIndex; + } + if(nestedBrackets > 0 || endIndex == text.length()) return; + if (endIndex >= startIndex) { + //final String name = text.substring(startIndex, endIndex); + refs.add(new AntDomMacrodefAttributeReference(element, new TextRange(valueBeginingOffset + startIndex, valueBeginingOffset + endIndex))); + } + endIndex = startIndex; + } + } + } diff --git a/plugins/ant/tests/src/com/intellij/lang/ant/AntResolveTest.java b/plugins/ant/tests/src/com/intellij/lang/ant/AntResolveTest.java index b08879dd40ce..389515acb462 100644 --- a/plugins/ant/tests/src/com/intellij/lang/ant/AntResolveTest.java +++ b/plugins/ant/tests/src/com/intellij/lang/ant/AntResolveTest.java @@ -15,13 +15,21 @@ */ package com.intellij.lang.ant; +import com.intellij.lang.ant.config.AntConfigurationBase; +import com.intellij.lang.ant.dom.AntDomMacrodefAttribute; import com.intellij.openapi.application.PluginPathManager; import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.pom.PomTargetPsiElement; import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; import com.intellij.psi.PsiReference; +import com.intellij.psi.xml.XmlFile; import com.intellij.psi.xml.XmlTag; import com.intellij.testFramework.ResolveTestCase; +import com.intellij.util.xml.DomElement; +import com.intellij.util.xml.DomTarget; +import com.intellij.util.xml.DomUtil; public class AntResolveTest extends ResolveTestCase { @@ -217,10 +225,15 @@ public class AntResolveTest extends ResolveTestCase { public void testBuildNumber() throws Exception { doPropertyTest(); } - // TODO: implement functionality - public void _testPropertyInMacrodefParam() throws Exception { + + public void testPropertyInMacrodefParam() throws Exception { PsiReference ref = configure(); - assertNotNull(ref.resolve()); + final PsiElement result = ref.resolve(); + assertTrue(result instanceof PomTargetPsiElement); + final PsiElement naviElem = ((DomTarget)((PomTargetPsiElement)result).getTarget()).getNavigationElement(); + final DomElement domElement = DomUtil.getDomElement(naviElem); + assertNotNull(domElement); + assertNotNull(domElement.getParentOfType(AntDomMacrodefAttribute.class, false)); } public void testMacrodefElement() throws Exception { @@ -228,24 +241,28 @@ public class AntResolveTest extends ResolveTestCase { assertNotNull(ref.resolve()); } - /* todo: rewrite into DOM public void testAntFilePropertyWithContexts() throws Exception { - final AntPropertyReference refImporting = (AntPropertyReference)configureByFile("PropertyAntFileImporting.ant"); - final AntFile importing = refImporting.getElement().getAntFile(); + final PsiReference refImporting = configureByFile("PropertyAntFileImporting.ant"); + final PsiFile importing = refImporting.getElement().getContainingFile(); + assertTrue(importing instanceof XmlFile); + final VirtualFile vFile = importing.getVirtualFile(); assertTrue(vFile != null); - final AntPropertyReference refImported = (AntPropertyReference)configureByFile("PropertyAntFileImported.ant", vFile.getParent()); - final AntFile imported = refImported.getElement().getAntFile(); - - AntConfigurationBase.getInstance(getProject()).setContextFile(imported, importing); - importing.clearCaches(); // need this because imported file was created after the importing + final PsiReference refImported = configureByFile("PropertyAntFileImported.ant", vFile.getParent()); + + final PsiFile imported = refImported.getElement().getContainingFile(); + assertTrue(imported instanceof XmlFile); + + final VirtualFile importedVFile = imported.getVirtualFile(); + assertTrue(importedVFile != null); + + AntConfigurationBase.getInstance(getProject()).setContextFile((XmlFile)imported, (XmlFile)importing); assertTrue(refImported.resolve() != null); assertTrue(refImporting.resolve() != null); } - */ private void doTargetTest() throws Exception { PsiReference ref = configure(); diff --git a/resources-en/src/messages/AntBundle.properties b/resources-en/src/messages/AntBundle.properties index f629d9cd5224..cb0fe13d45cd 100644 --- a/resources-en/src/messages/AntBundle.properties +++ b/resources-en/src/messages/AntBundle.properties @@ -161,6 +161,7 @@ nested.element.is.not.allowed.here=Nested element ''{0}'' is not allowed here cannot.resolve.target=Target ''{0}'' is unknown cannot.resolve.refid=Cannot resolve ''{0}'' refid unknown.property=Property ''{0}'' is unknown +unknown.macro.attribute=Macro attribute ''{0}'' is unknown file.doesnt.exist=''{0}'' does not exist integer.attribute.has.invalid.value=Attribute ''{0}'' should have an integer value intention.create.target.family.name=Create target