| /* |
| * Copyright 2000-2009 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.intellij.plugins.intelliLang.inject.xml; |
| |
| import com.intellij.lang.Language; |
| import com.intellij.openapi.actionSystem.AnAction; |
| import com.intellij.openapi.actionSystem.AnActionEvent; |
| import com.intellij.openapi.options.Configurable; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.ui.DialogBuilder; |
| import com.intellij.openapi.ui.DialogWrapper; |
| import com.intellij.openapi.util.Factory; |
| import com.intellij.openapi.util.Ref; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.patterns.*; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.psi.PsiLanguageInjectionHost; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.psi.xml.*; |
| import com.intellij.util.Consumer; |
| import com.intellij.util.PlatformIcons; |
| import com.intellij.util.containers.ContainerUtil; |
| import org.intellij.plugins.intelliLang.Configuration; |
| import org.intellij.plugins.intelliLang.inject.AbstractLanguageInjectionSupport; |
| import org.intellij.plugins.intelliLang.inject.EditInjectionSettingsAction; |
| import org.intellij.plugins.intelliLang.inject.InjectLanguageAction; |
| import org.intellij.plugins.intelliLang.inject.InjectorUtils; |
| import org.intellij.plugins.intelliLang.inject.config.*; |
| import org.intellij.plugins.intelliLang.inject.config.ui.AbstractInjectionPanel; |
| import org.intellij.plugins.intelliLang.inject.config.ui.XmlAttributePanel; |
| import org.intellij.plugins.intelliLang.inject.config.ui.XmlTagPanel; |
| import org.jdom.Element; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| |
| /** |
| * @author Gregory.Shrago |
| */ |
| public class XmlLanguageInjectionSupport extends AbstractLanguageInjectionSupport { |
| |
| @NonNls public static final String XML_SUPPORT_ID = "xml"; |
| |
| private static boolean isMine(final PsiLanguageInjectionHost host) { |
| if (host instanceof XmlAttributeValue) { |
| final PsiElement p = host.getParent(); |
| if (p instanceof XmlAttribute) { |
| final String s = ((XmlAttribute)p).getName(); |
| return !("xmlns".equals(s) || s.startsWith("xmlns:")); |
| } |
| } |
| else if (host instanceof XmlText) { |
| final XmlTag tag = ((XmlText)host).getParentTag(); |
| return tag != null/* && tag.getValue().getTextElements().length == 1 && tag.getSubTags().length == 0*/; |
| } |
| return false; |
| } |
| |
| @NotNull |
| public String getId() { |
| return XML_SUPPORT_ID; |
| } |
| |
| @NotNull |
| public Class[] getPatternClasses() { |
| return new Class[] {XmlPatterns.class}; |
| } |
| |
| @Override |
| public boolean isApplicableTo(PsiLanguageInjectionHost host) { |
| return host instanceof XmlElement; |
| } |
| |
| @Nullable |
| @Override |
| public BaseInjection findCommentInjection(@NotNull PsiElement host, @Nullable Ref<PsiElement> commentRef) { |
| if (host instanceof XmlAttributeValue) return null; |
| return InjectorUtils.findCommentInjection(host instanceof XmlText ? host.getParent() : host, getId(), commentRef); |
| } |
| |
| @Override |
| public boolean addInjectionInPlace(Language language, final PsiLanguageInjectionHost psiElement) { |
| if (!isMine(psiElement)) return false; |
| String id = language.getID(); |
| if (psiElement instanceof XmlAttributeValue) { |
| return doInjectInAttributeValue((XmlAttributeValue)psiElement, id); |
| } |
| else if (psiElement instanceof XmlText) { |
| return doInjectInXmlText((XmlText)psiElement, id); |
| } |
| return false; |
| } |
| |
| public boolean removeInjectionInPlace(final PsiLanguageInjectionHost host) { |
| return removeInjection(host); |
| } |
| |
| @Override |
| public boolean removeInjection(PsiElement host) { |
| final Project project = host.getProject(); |
| final Configuration configuration = Configuration.getProjectInstance(project); |
| final ArrayList<BaseInjection> injections = collectInjections(host, configuration); |
| if (injections.isEmpty()) return false; |
| final ArrayList<BaseInjection> newInjections = new ArrayList<BaseInjection>(); |
| for (BaseInjection injection : injections) { |
| final BaseInjection newInjection = injection.copy(); |
| newInjection.setPlaceEnabled(null, false); |
| if (InjectorUtils.canBeRemoved(newInjection)) continue; |
| newInjections.add(newInjection); |
| } |
| configuration.replaceInjectionsWithUndo( |
| project, newInjections, injections, Collections.<PsiElement>emptyList()); |
| return true; |
| } |
| |
| public boolean editInjectionInPlace(final PsiLanguageInjectionHost host) { |
| if (!isMine(host)) return false; |
| final Project project = host.getProject(); |
| final Configuration configuration = Configuration.getProjectInstance(project); |
| final ArrayList<BaseInjection> injections = collectInjections(host, configuration); |
| if (injections.isEmpty()) return false; |
| final BaseInjection originalInjection = injections.get(0); |
| final BaseInjection xmlInjection = createFrom(originalInjection); |
| final BaseInjection newInjection = |
| xmlInjection == null? showDefaultInjectionUI(project, originalInjection.copy()) : showInjectionUI(project, xmlInjection); |
| if (newInjection != null) { |
| configuration.replaceInjectionsWithUndo( |
| project, Collections.singletonList(newInjection), |
| Collections.singletonList(originalInjection), |
| Collections.<PsiElement>emptyList()); |
| } |
| return true; |
| } |
| |
| @Nullable |
| private static BaseInjection showInjectionUI(final Project project, final BaseInjection xmlInjection) { |
| final DialogBuilder builder = new DialogBuilder(project); |
| final AbstractInjectionPanel panel; |
| if (xmlInjection instanceof XmlTagInjection) { |
| panel = new XmlTagPanel((XmlTagInjection)xmlInjection, project); |
| builder.setHelpId("reference.settings.injection.language.injection.settings.xml.tag"); |
| } |
| else if (xmlInjection instanceof XmlAttributeInjection) { |
| panel = new XmlAttributePanel((XmlAttributeInjection)xmlInjection, project); |
| builder.setHelpId("reference.settings.injection.language.injection.settings.xml.attribute"); |
| } |
| else throw new AssertionError(); |
| panel.reset(); |
| builder.addOkAction(); |
| builder.addCancelAction(); |
| builder.setCenterPanel(panel.getComponent()); |
| builder.setTitle(EditInjectionSettingsAction.EDIT_INJECTION_TITLE); |
| builder.setOkOperation(new Runnable() { |
| public void run() { |
| panel.apply(); |
| builder.getDialogWrapper().close(DialogWrapper.OK_EXIT_CODE); |
| } |
| }); |
| if (builder.show() == DialogWrapper.OK_EXIT_CODE) { |
| return xmlInjection.copy(); |
| } |
| return null; |
| } |
| |
| @Nullable |
| private static BaseInjection createFrom(final BaseInjection injection) { |
| if (injection.getInjectionPlaces().length == 0 || injection.getInjectionPlaces().length > 1) return null; |
| |
| AbstractTagInjection result; |
| final InjectionPlace place = injection.getInjectionPlaces()[0]; |
| final ElementPattern<PsiElement> rootPattern = place.getElementPattern(); |
| final ElementPatternCondition<PsiElement> rootCondition = rootPattern.getCondition(); |
| final Class<PsiElement> elementClass = rootCondition.getInitialCondition().getAcceptedClass(); |
| if (XmlAttribute.class.equals(elementClass)) { |
| result = new XmlAttributeInjection().copyFrom(injection); |
| } |
| else if (XmlTag.class.equals(elementClass)) { |
| result = new XmlTagInjection().copyFrom(injection); |
| } |
| else return null; |
| result.setInjectionPlaces(InjectionPlace.EMPTY_ARRAY); |
| for (PatternCondition<? super PsiElement> condition : rootCondition.getConditions()) { |
| final String value = extractValue(condition); |
| if ("withLocalName".equals(condition.getDebugMethodName())) { |
| if (value == null) return null; |
| if (result instanceof XmlAttributeInjection) { |
| ((XmlAttributeInjection)result).setAttributeName(value); |
| } |
| else { |
| result.setTagName(value); |
| } |
| } |
| else if ("withNamespace".equals(condition.getDebugMethodName())) { |
| if (value == null) return null; |
| if (result instanceof XmlAttributeInjection) { |
| ((XmlAttributeInjection)result).setAttributeNamespace(value); |
| } |
| else { |
| result.setTagNamespace(value); |
| } |
| } |
| else if (result instanceof XmlAttributeInjection && condition instanceof PatternConditionPlus) { |
| boolean strict = "withParent".equals(condition.getDebugMethodName()); |
| if (!strict && !"inside".equals(condition.getDebugMethodName())) return null; |
| |
| result.setApplyToSubTags(!strict); |
| ElementPattern<?> insidePattern = ((PatternConditionPlus)condition).getValuePattern(); |
| if (!XmlTag.class.equals(insidePattern.getCondition().getInitialCondition().getAcceptedClass())) return null; |
| for (PatternCondition<?> insideCondition : insidePattern.getCondition().getConditions()) { |
| String tagValue = extractValue(insideCondition); |
| if (tagValue == null) return null; |
| if ("withLocalName".equals(insideCondition.getDebugMethodName())) { |
| result.setTagName(tagValue); |
| } |
| else if ("withNamespace".equals(insideCondition.getDebugMethodName())) { |
| result.setTagNamespace(tagValue); |
| } |
| } |
| } |
| else { |
| return null; |
| } |
| } |
| result.generatePlaces(); |
| return result; |
| } |
| |
| @Nullable |
| private static String extractValue(PatternCondition<?> condition) { |
| if (!(condition instanceof PatternConditionPlus)) return null; |
| final ElementPattern valuePattern = ((PatternConditionPlus)condition).getValuePattern(); |
| final ElementPatternCondition<?> rootCondition = valuePattern.getCondition(); |
| if (!String.class.equals(rootCondition.getInitialCondition().getAcceptedClass())) return null; |
| if (rootCondition.getConditions().size() != 1) return null; |
| final PatternCondition<?> valueCondition = rootCondition.getConditions().get(0); |
| if (!(valueCondition instanceof ValuePatternCondition<?>)) return null; |
| final Collection values = ((ValuePatternCondition)valueCondition).getValues(); |
| if (values.size() == 1) { |
| final Object value = values.iterator().next(); |
| return value instanceof String? (String)value : null; |
| } |
| else if (!values.isEmpty()) { |
| for (Object value : values) { |
| if (!(value instanceof String)) return null; |
| } |
| //noinspection unchecked |
| return StringUtil.join(values, "|"); |
| } |
| return null; |
| } |
| |
| public BaseInjection createInjection(Element element) { |
| String place = StringUtil.notNullize(element.getChildText("place"), ""); |
| if (place.startsWith("xmlAttribute")) { |
| return new XmlAttributeInjection(); |
| } |
| else if (place.startsWith("xmlTag")) { |
| return new XmlTagInjection(); |
| } |
| else { |
| return new BaseInjection(XML_SUPPORT_ID); |
| } |
| } |
| |
| public Configurable[] createSettings(final Project project, final Configuration configuration) { |
| return new Configurable[0]; |
| } |
| |
| private static boolean doInjectInXmlText(final XmlText host, final String languageId) { |
| final XmlTag tag = host.getParentTag(); |
| if (tag != null) { |
| final XmlTagInjection injection = new XmlTagInjection(); |
| injection.setInjectedLanguageId(languageId); |
| injection.setTagName(tag.getLocalName()); |
| injection.setTagNamespace(tag.getNamespace()); |
| injection.generatePlaces(); |
| doEditInjection(host.getProject(), injection); |
| return true; |
| } |
| return false; |
| } |
| |
| private static void doEditInjection(final Project project, final XmlTagInjection template) { |
| final Configuration configuration = InjectorUtils.getEditableInstance(project); |
| final AbstractTagInjection originalInjection = (AbstractTagInjection)configuration.findExistingInjection(template); |
| |
| final XmlTagInjection newInjection = originalInjection == null? template : new XmlTagInjection().copyFrom(originalInjection); |
| configuration.replaceInjectionsWithUndo( |
| project, Collections.singletonList(newInjection), |
| ContainerUtil.createMaybeSingletonList(originalInjection), |
| Collections.<PsiElement>emptyList()); |
| } |
| |
| private static boolean doInjectInAttributeValue(final XmlAttributeValue host, final String languageId) { |
| final XmlAttribute attribute = PsiTreeUtil.getParentOfType(host, XmlAttribute.class, true); |
| final XmlTag tag = attribute == null? null : attribute.getParent(); |
| if (tag != null) { |
| final XmlAttributeInjection injection = new XmlAttributeInjection(); |
| injection.setInjectedLanguageId(languageId); |
| injection.setAttributeName(attribute.getLocalName()); |
| injection.setAttributeNamespace(attribute.getNamespace()); |
| injection.setTagName(tag.getLocalName()); |
| injection.setTagNamespace(tag.getNamespace()); |
| injection.generatePlaces(); |
| doEditInjection(host.getProject(), injection); |
| return true; |
| } |
| return false; |
| } |
| |
| private static void doEditInjection(final Project project, final XmlAttributeInjection template) { |
| final Configuration configuration = InjectorUtils.getEditableInstance(project); |
| final BaseInjection originalInjection = configuration.findExistingInjection(template); |
| final BaseInjection newInjection = originalInjection == null ? template : originalInjection.copy(); |
| configuration.replaceInjectionsWithUndo( |
| project, Collections.singletonList(newInjection), |
| ContainerUtil.createMaybeSingletonList(originalInjection), |
| Collections.<PsiElement>emptyList()); |
| } |
| |
| private static ArrayList<BaseInjection> collectInjections(final PsiElement host, |
| final Configuration configuration) { |
| final ArrayList<BaseInjection> result = new ArrayList<BaseInjection>(); |
| final PsiElement element = host instanceof XmlText? ((XmlText)host).getParentTag() : |
| host instanceof XmlAttributeValue? host.getParent(): host; |
| for (BaseInjection injection : configuration.getInjections(XML_SUPPORT_ID)) { |
| if (injection.acceptsPsiElement(element)) { |
| result.add(injection); |
| } |
| } |
| return result; |
| } |
| |
| @Override |
| public AnAction[] createAddActions(final Project project, final Consumer<BaseInjection> consumer) { |
| return new AnAction[] { |
| new AnAction("XML Tag Injection", null, PlatformIcons.XML_TAG_ICON) { |
| @Override |
| public void actionPerformed(final AnActionEvent e) { |
| final BaseInjection newInjection = showInjectionUI(project, new XmlTagInjection()); |
| if (newInjection != null) consumer.consume(newInjection); |
| } |
| }, |
| new AnAction("XML Attribute Injection", null, PlatformIcons.ANNOTATION_TYPE_ICON) { |
| @Override |
| public void actionPerformed(final AnActionEvent e) { |
| final BaseInjection injection = showInjectionUI(project, new XmlAttributeInjection()); |
| if (injection != null) consumer.consume(injection); |
| } |
| } |
| }; |
| } |
| |
| @Override |
| public AnAction createEditAction(final Project project, final Factory<BaseInjection> producer) { |
| return new AnAction() { |
| @Override |
| public void actionPerformed(final AnActionEvent e) { |
| final BaseInjection originalInjection = producer.create(); |
| final BaseInjection injection = createFrom(originalInjection); |
| if (injection != null) { |
| final BaseInjection newInjection = showInjectionUI(project, injection); |
| if (newInjection != null) { |
| originalInjection.copyFrom(newInjection); |
| } |
| } |
| else { |
| createDefaultEditAction(project, producer).actionPerformed(null); |
| } |
| } |
| }; |
| } |
| } |