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
;
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.
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.
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
;
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
);
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() {
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()) {
115 for (Trinity
<PsiLanguageInjectionHost
, InjectedLanguage
, TextRange
> trinity
: result
) {
116 processor
.process(language
, Collections
.singletonList(trinity
));
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()) {
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:
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
) {
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
));
162 for (Trinity
<PsiLanguageInjectionHost
, InjectedLanguage
, TextRange
> trinity
: result
) {
163 processor
.process(language
, Collections
.singletonList(trinity
));
167 processor
.process(language
, result
);
169 if (injection
.isTerminal()) {