18722 assert: CaretModelImpl.validateCallContext
[fedora-idea.git] / plugins / IntelliLang / src / org / intellij / plugins / intelliLang / inject / xml / XmlLanguageInjector.java
blob35e3b186a4ced1e871909981a0bb15d1b593322f
1 /*
2 * Copyright 2006 Sascha Weinreuter
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package org.intellij.plugins.intelliLang.inject.xml;
19 import com.intellij.lang.Language;
20 import com.intellij.lang.injection.MultiHostInjector;
21 import com.intellij.lang.injection.MultiHostRegistrar;
22 import com.intellij.openapi.util.Ref;
23 import com.intellij.openapi.util.TextRange;
24 import com.intellij.openapi.util.Trinity;
25 import com.intellij.openapi.util.text.StringUtil;
26 import com.intellij.psi.*;
27 import com.intellij.psi.xml.*;
28 import com.intellij.util.PairProcessor;
29 import org.intellij.plugins.intelliLang.Configuration;
30 import org.intellij.plugins.intelliLang.inject.InjectedLanguage;
31 import org.intellij.plugins.intelliLang.inject.LanguageInjectionSupport;
32 import org.intellij.plugins.intelliLang.inject.InjectorUtils;
33 import org.intellij.plugins.intelliLang.inject.config.AbstractTagInjection;
34 import org.intellij.plugins.intelliLang.inject.config.BaseInjection;
35 import org.jetbrains.annotations.NotNull;
37 import java.util.*;
39 /**
40 * This is the main part of the injection code. The component registers a language injector, the reference provider that
41 * supplies completions for language-IDs and regular expression enum-values as well as the Quick Edit action.
42 * <p/>
43 * The injector obtains the static injection configuration for each XML tag, attribute or String literal and also
44 * dynamically computes the prefix/suffix for the language fragment from binary expressions.
45 * <p/>
46 * It also tries to deal with the "glued token" problem by removing or adding whitespace to the prefix/suffix.
48 public final class XmlLanguageInjector implements MultiHostInjector {
50 private final Configuration myInjectionConfiguration;
52 public XmlLanguageInjector(Configuration configuration) {
53 myInjectionConfiguration = configuration;
56 @NotNull
57 public List<? extends Class<? extends PsiElement>> elementsToInjectIn() {
58 return Arrays.asList(XmlTag.class, XmlAttribute.class);
61 public void getLanguagesToInject(@NotNull final MultiHostRegistrar registrar, @NotNull PsiElement host) {
62 final TreeSet<TextRange> ranges = new TreeSet<TextRange>(InjectorUtils.RANGE_COMPARATOR);
63 final PsiFile containingFile = host.getContainingFile();
64 getInjectedLanguage(host, new PairProcessor<Language, List<Trinity<PsiLanguageInjectionHost, InjectedLanguage, TextRange>>>() {
65 public boolean process(final Language language, List<Trinity<PsiLanguageInjectionHost, InjectedLanguage, TextRange>> list) {
66 for (Trinity<PsiLanguageInjectionHost, InjectedLanguage, TextRange> trinity : list) {
67 if (ranges.contains(trinity.third.shiftRight(trinity.first.getTextRange().getStartOffset()))) return true;
69 for (Trinity<PsiLanguageInjectionHost, InjectedLanguage, TextRange> trinity : list) {
70 final PsiLanguageInjectionHost host = trinity.first;
71 if (host.getContainingFile() != containingFile) continue;
72 final TextRange textRange = trinity.third;
73 ranges.add(textRange.shiftRight(host.getTextRange().getStartOffset()));
75 InjectorUtils.registerInjection(language, list, containingFile, registrar);
76 return true;
78 });
82 void getInjectedLanguage(final PsiElement place, final PairProcessor<Language, List<Trinity<PsiLanguageInjectionHost, InjectedLanguage, TextRange>>> processor) {
83 if (place instanceof XmlTag) {
84 final XmlTag xmlTag = (XmlTag)place;
85 for (final BaseInjection injection : myInjectionConfiguration.getInjections(LanguageInjectionSupport.XML_SUPPORT_ID)) {
86 if (injection.acceptsPsiElement(xmlTag)) {
87 final Language language = InjectedLanguage.findLanguageById(injection.getInjectedLanguageId());
88 if (language == null) continue;
89 final boolean separateFiles = !injection.isSingleFile() && StringUtil.isNotEmpty(injection.getValuePattern());
91 final Ref<Boolean> hasSubTags = Ref.create(Boolean.FALSE);
92 final List<Trinity<PsiLanguageInjectionHost, InjectedLanguage, TextRange>> result = new ArrayList<Trinity<PsiLanguageInjectionHost, InjectedLanguage, TextRange>>();
94 xmlTag.acceptChildren(new PsiElementVisitor() {
95 @Override
96 public void visitElement(final PsiElement element) {
97 if (element instanceof XmlText) {
98 if (element.getTextLength() == 0) return;
99 final List<TextRange> list = injection.getInjectedArea(element);
100 final InjectedLanguage l = InjectedLanguage.create(injection.getInjectedLanguageId(), injection.getPrefix(), injection.getSuffix(), false);
101 for (TextRange textRange : list) {
102 result.add(Trinity.create((PsiLanguageInjectionHost)element, l, textRange));
105 else if (element instanceof XmlTag) {
106 hasSubTags.set(Boolean.TRUE);
107 if (injection instanceof AbstractTagInjection && ((AbstractTagInjection)injection).isApplyToSubTagTexts()) {
108 element.acceptChildren(this);
113 if (!result.isEmpty()) {
114 if (separateFiles) {
115 for (Trinity<PsiLanguageInjectionHost, InjectedLanguage, TextRange> trinity : result) {
116 processor.process(language, Collections.singletonList(trinity));
119 else {
120 for (Trinity<PsiLanguageInjectionHost, InjectedLanguage, TextRange> trinity : result) {
121 trinity.first.putUserData(LanguageInjectionSupport.HAS_UNPARSABLE_FRAGMENTS, hasSubTags.get());
123 processor.process(language, result);
126 if (injection.isTerminal()) {
127 break;
132 else if (place instanceof XmlAttribute) {
133 final XmlAttribute attribute = (XmlAttribute)place;
134 final XmlAttributeValue value = attribute.getValueElement();
135 if (value == null) return;
136 // Check that we don't inject anything into embedded (e.g. JavaScript) content:
137 // XmlToken: "
138 // JSEmbeddedContent
139 // XmlToken "
141 // Actually IDEA shouldn't ask for injected languages at all in this case.
142 final PsiElement[] children = value.getChildren();
143 if (children.length < 3 || !(children[1] instanceof XmlToken) ||
144 ((XmlToken)children[1]).getTokenType() != XmlTokenType.XML_ATTRIBUTE_VALUE_TOKEN) {
145 return;
148 for (BaseInjection injection : myInjectionConfiguration.getInjections(LanguageInjectionSupport.XML_SUPPORT_ID)) {
149 if (injection.acceptsPsiElement(attribute)) {
150 final Language language = InjectedLanguage.findLanguageById(injection.getInjectedLanguageId());
151 if (language == null) continue;
152 final boolean separateFiles = !injection.isSingleFile() && StringUtil.isNotEmpty(injection.getValuePattern());
154 final List<TextRange> ranges = injection.getInjectedArea(value);
155 if (ranges.isEmpty()) continue;
156 final List<Trinity<PsiLanguageInjectionHost, InjectedLanguage, TextRange>> result = new ArrayList<Trinity<PsiLanguageInjectionHost, InjectedLanguage, TextRange>>();
157 final InjectedLanguage l = InjectedLanguage.create(injection.getInjectedLanguageId(), injection.getPrefix(), injection.getSuffix(), false);
158 for (TextRange textRange : ranges) {
159 result.add(Trinity.create((PsiLanguageInjectionHost)value, l, textRange));
161 if (separateFiles) {
162 for (Trinity<PsiLanguageInjectionHost, InjectedLanguage, TextRange> trinity : result) {
163 processor.process(language, Collections.singletonList(trinity));
166 else {
167 processor.process(language, result);
169 if (injection.isTerminal()) {
170 break;