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
;
19 import com
.intellij
.lang
.Language
;
20 import com
.intellij
.lang
.injection
.InjectedLanguageManager
;
21 import com
.intellij
.lang
.injection
.MultiHostInjector
;
22 import com
.intellij
.lang
.injection
.MultiHostRegistrar
;
23 import com
.intellij
.openapi
.components
.ProjectComponent
;
24 import com
.intellij
.openapi
.diagnostic
.Logger
;
25 import com
.intellij
.openapi
.extensions
.Extensions
;
26 import com
.intellij
.openapi
.project
.Project
;
27 import com
.intellij
.openapi
.util
.*;
28 import com
.intellij
.openapi
.util
.text
.StringUtil
;
29 import com
.intellij
.psi
.*;
30 import com
.intellij
.psi
.filters
.TrueFilter
;
31 import com
.intellij
.psi
.impl
.source
.resolve
.reference
.ReferenceProvidersRegistry
;
32 import com
.intellij
.psi
.search
.LocalSearchScope
;
33 import com
.intellij
.psi
.search
.searches
.ReferencesSearch
;
34 import com
.intellij
.psi
.tree
.IElementType
;
35 import com
.intellij
.psi
.util
.PsiTreeUtil
;
36 import com
.intellij
.psi
.util
.PsiUtil
;
37 import com
.intellij
.psi
.xml
.*;
38 import com
.intellij
.util
.NullableFunction
;
39 import com
.intellij
.util
.PairProcessor
;
40 import com
.intellij
.util
.containers
.ContainerUtil
;
41 import com
.intellij
.xml
.util
.XmlUtil
;
42 import gnu
.trove
.THashSet
;
43 import org
.intellij
.plugins
.intelliLang
.Configuration
;
44 import org
.intellij
.plugins
.intelliLang
.inject
.config
.MethodParameterInjection
;
45 import org
.intellij
.plugins
.intelliLang
.inject
.config
.XmlAttributeInjection
;
46 import org
.intellij
.plugins
.intelliLang
.inject
.config
.XmlTagInjection
;
47 import org
.intellij
.plugins
.intelliLang
.util
.AnnotationUtilEx
;
48 import org
.intellij
.plugins
.intelliLang
.util
.ContextComputationProcessor
;
49 import org
.intellij
.plugins
.intelliLang
.util
.PsiUtilEx
;
50 import org
.jetbrains
.annotations
.NotNull
;
51 import org
.jetbrains
.annotations
.Nullable
;
56 * This is the main part of the injection code. The component registers a language injector, the reference provider that
57 * supplies completions for language-IDs and regular expression enum-values as well as the Quick Edit action.
59 * The injector obtains the static injection configuration for each XML tag, attribute or String literal and also
60 * dynamically computes the prefix/suffix for the language fragment from binary expressions.
62 * It also tries to deal with the "glued token" problem by removing or adding whitespace to the prefix/suffix.
64 public final class CustomLanguageInjector
implements ProjectComponent
{
65 private static final Comparator
<TextRange
> RANGE_COMPARATOR
= new Comparator
<TextRange
>() {
66 public int compare(final TextRange o1
, final TextRange o2
) {
67 if (o1
.intersects(o2
)) return 0;
68 return o1
.getStartOffset() - o2
.getStartOffset();
72 private final Project myProject
;
73 private final Configuration myInjectionConfiguration
;
74 private long myConfigurationModificationCount
;
75 private MultiValuesMap
<Trinity
<String
, Integer
, Integer
>, MethodParameterInjection
> myMethodCache
;
77 @SuppressWarnings({"unchecked"})
78 private final List
<Pair
<SmartPsiElementPointer
<PsiLanguageInjectionHost
>, InjectedLanguage
>> myTempPlaces
= new ArrayList();
79 static final Key
<Boolean
> HAS_UNPARSABLE_FRAGMENTS
= Key
.create("HAS_UNPARSABLE_FRAGMENTS");
81 public CustomLanguageInjector(Project project
, Configuration configuration
) {
83 myInjectionConfiguration
= configuration
;
86 public void initComponent() {
87 InjectedLanguageManager
.getInstance(myProject
).registerMultiHostInjector(new MyLanguageInjector(this));
88 ReferenceProvidersRegistry
.getInstance(myProject
)
89 .registerReferenceProvider(TrueFilter
.INSTANCE
, PsiLiteralExpression
.class, new LanguageReferenceProvider());
93 private void getInjectedLanguage(final PsiElement place
, final PairProcessor
<Language
, List
<Trinity
<PsiLanguageInjectionHost
, InjectedLanguage
, TextRange
>>> processor
) {
95 if (place
instanceof PsiLiteralExpression
&& !isStringLiteral(place
)) return;
97 synchronized (myTempPlaces
) {
98 for (Iterator
<Pair
<SmartPsiElementPointer
<PsiLanguageInjectionHost
>, InjectedLanguage
>> it
= myTempPlaces
.iterator(); it
.hasNext();) {
99 final Pair
<SmartPsiElementPointer
<PsiLanguageInjectionHost
>, InjectedLanguage
> pair
= it
.next();
100 final PsiLanguageInjectionHost element
= pair
.first
.getElement();
101 if (element
== null) {
104 else if (element
== place
) {
105 processor
.process(pair
.second
.getLanguage(), Collections
.singletonList(Trinity
.create(element
, pair
.second
, ElementManipulators
.getManipulator(element
).getRangeInElement(element
))));
111 if (place
instanceof PsiExpression
) {
112 processLiteralExpressionInjections((PsiExpression
)place
, processor
);
114 else if (place
instanceof XmlTag
) {
115 final XmlTag xmlTag
= (XmlTag
)place
;
116 for (final XmlTagInjection injection
: myInjectionConfiguration
.getTagInjections()) {
117 if (injection
.isApplicable(xmlTag
)) {
118 final Ref
<Boolean
> hasSubTags
= Ref
.create(Boolean
.FALSE
);
119 final List
<Trinity
<PsiLanguageInjectionHost
, InjectedLanguage
, TextRange
>> result
= new ArrayList
<Trinity
<PsiLanguageInjectionHost
, InjectedLanguage
, TextRange
>>();
120 xmlTag
.acceptChildren(new PsiElementVisitor() {
122 public void visitElement(final PsiElement element
) {
123 if (element
instanceof XmlText
) {
124 if (element
.getTextLength() == 0) return;
125 final List
<TextRange
> list
= injection
.getInjectedArea((XmlText
)element
);
126 final InjectedLanguage l
= InjectedLanguage
.create(injection
.getInjectedLanguageId(), injection
.getPrefix(), injection
.getSuffix(), false);
127 for (TextRange textRange
: list
) {
128 result
.add(Trinity
.create((PsiLanguageInjectionHost
)element
, l
, textRange
));
131 else if (element
instanceof XmlTag
) {
132 hasSubTags
.set(Boolean
.TRUE
);
133 if (injection
.isApplyToSubTagTexts()) {
134 element
.acceptChildren(this);
139 if (!result
.isEmpty()) {
140 final Language language
= InjectedLanguage
.findLanguageById(injection
.getInjectedLanguageId());
141 if (language
== null) continue;
142 for (Trinity
<PsiLanguageInjectionHost
, InjectedLanguage
, TextRange
> trinity
: result
) {
143 trinity
.first
.putUserData(HAS_UNPARSABLE_FRAGMENTS
, hasSubTags
.get());
145 processor
.process(language
, result
);
147 if (injection
.isTerminal()) {
153 else if (place
instanceof XmlAttributeValue
) {
154 final XmlAttributeValue value
= (XmlAttributeValue
)place
;
156 // Check that we don't inject anything into embedded (e.g. JavaScript) content:
161 // Actually IDEA shouldn't ask for injected languages at all in this case.
162 final PsiElement
[] children
= value
.getChildren();
163 if (children
.length
< 3 || !(children
[1] instanceof XmlToken
) ||
164 ((XmlToken
)children
[1]).getTokenType() != XmlTokenType
.XML_ATTRIBUTE_VALUE_TOKEN
) {
168 for (XmlAttributeInjection injection
: myInjectionConfiguration
.getAttributeInjections()) {
169 if (injection
.isApplicable(value
)) {
170 final List
<TextRange
> ranges
= injection
.getInjectedArea(value
);
171 if (ranges
.isEmpty()) continue;
172 final Language language
= InjectedLanguage
.findLanguageById(injection
.getInjectedLanguageId());
173 if (language
== null) continue;
174 final List
<Trinity
<PsiLanguageInjectionHost
, InjectedLanguage
, TextRange
>> result
= new ArrayList
<Trinity
<PsiLanguageInjectionHost
, InjectedLanguage
, TextRange
>>();
175 final InjectedLanguage l
= InjectedLanguage
.create(injection
.getInjectedLanguageId(), injection
.getPrefix(), injection
.getSuffix(), false);
176 for (TextRange textRange
: ranges
) {
177 result
.add(Trinity
.create((PsiLanguageInjectionHost
)value
, l
, textRange
));
179 processor
.process(language
, result
);
180 if (injection
.isTerminal()) {
187 for (CustomLanguageInjectorExtension o
: Extensions
.getExtensions(CustomLanguageInjectorExtension
.EP_NAME
)) {
188 o
.getInjectedLanguage(myInjectionConfiguration
, place
, processor
);
193 private static Collection
<PsiLiteralExpression
> findLiteralExpressions(final PsiElement place
, final boolean resolveReferences
, final Ref
<Boolean
> concatFlag
) {
194 if (place
instanceof PsiReferenceExpression
) {
195 if (resolveReferences
) {
196 final ArrayList
<PsiLiteralExpression
> list
= new ArrayList
<PsiLiteralExpression
>();
197 final JavaResolveResult
[] results
= ((PsiReferenceExpression
)place
).multiResolve(true);
198 for (JavaResolveResult result
: results
) {
199 final PsiElement element
= result
.getElement();
200 if (element
instanceof PsiVariable
) {
201 final PsiVariable psiVariable
= (PsiVariable
)element
;
202 final PsiType type
= psiVariable
.getType();
203 if (!PsiUtilEx
.isStringOrStringArray(type
)) continue;
204 boolean isArray
= type
instanceof PsiArrayType
;
205 for (PsiExpression expression
: getVariableAssignmentsInFile(psiVariable
, concatFlag
)) {
207 if (expression
instanceof PsiNewExpression
) {
208 final PsiArrayInitializerExpression arrayInit
= ((PsiNewExpression
)expression
).getArrayInitializer();
209 if (arrayInit
!= null) {
210 for (PsiExpression psiExpression
: arrayInit
.getInitializers()) {
211 ContainerUtil
.addIfNotNull(findFirstLiteralExpression(psiExpression
), list
);
217 ContainerUtil
.addIfNotNull(findFirstLiteralExpression(expression
), list
);
225 else if (place
instanceof PsiNewExpression
) {
226 final ArrayList
<PsiLiteralExpression
> list
= new ArrayList
<PsiLiteralExpression
>();
227 final PsiArrayInitializerExpression arrayInit
= ((PsiNewExpression
)place
).getArrayInitializer();
228 if (arrayInit
!= null) {
229 for (PsiExpression psiExpression
: arrayInit
.getInitializers()) {
230 ContainerUtil
.addIfNotNull(findFirstLiteralExpression(psiExpression
), list
);
235 else if (place
instanceof PsiExpression
) {
236 final PsiLiteralExpression expression
= findFirstLiteralExpression((PsiExpression
)place
);
237 if (expression
!= null) return Collections
.singletonList(expression
);
239 return Collections
.emptyList();
242 private static boolean isStringLiteral(final PsiElement place
) {
243 if (place
instanceof PsiLiteralExpression
) {
244 final PsiElement child
= place
.getFirstChild();
245 if (child
!= null && child
instanceof PsiJavaToken
) {
246 if (((PsiJavaToken
)child
).getTokenType() == JavaTokenType
.STRING_LITERAL
) {
255 private static PsiLiteralExpression
findFirstLiteralExpression(final PsiExpression expression
) {
256 if (isStringLiteral(expression
)) return (PsiLiteralExpression
)expression
;
257 final LinkedList
<PsiElement
> list
= new LinkedList
<PsiElement
>();
258 list
.add(expression
);
259 while (!list
.isEmpty()) {
260 final PsiElement element
= list
.removeFirst();
261 if (element
instanceof PsiCallExpression
) continue; // IDEADEV-28384 - TODO: other cases?
262 if (isStringLiteral(element
)) {
263 return (PsiLiteralExpression
)element
;
265 list
.addAll(0, Arrays
.asList(element
.getChildren()));
270 private void processLiteralExpressionInjections(final PsiExpression place
, final PairProcessor
<Language
, List
<Trinity
<PsiLanguageInjectionHost
, InjectedLanguage
, TextRange
>>> processor
) {
271 final PsiElement topBlock
= PsiUtil
.getTopLevelEnclosingCodeBlock(place
, null);
272 final LocalSearchScope searchScope
= new LocalSearchScope(new PsiElement
[]{topBlock
instanceof PsiCodeBlock? topBlock
: place
.getContainingFile()}, "", true);
273 final LinkedList
<PsiExpression
> places
= new LinkedList
<PsiExpression
>();
275 while (!places
.isEmpty()) {
276 final PsiExpression curPlace
= places
.removeFirst();
277 final PsiModifierListOwner owner
= AnnotationUtilEx
.getAnnotatedElementFor(curPlace
, AnnotationUtilEx
.LookupType
.PREFER_CONTEXT
);
278 if (owner
== null) continue;
279 if (processAnnotationInjections(place
, owner
, processor
)) return; // annotated element
281 final PsiMethod psiMethod
;
282 final Trinity
<String
, Integer
, Integer
> trin
;
283 if (owner
instanceof PsiParameter
) {
284 psiMethod
= PsiTreeUtil
.getParentOfType(owner
, PsiMethod
.class, false);
285 if (psiMethod
== null) continue;
286 trin
= Trinity
.create(psiMethod
.getName(), psiMethod
.getParameterList().getParametersCount(),
287 psiMethod
.getParameterList().getParameterIndex((PsiParameter
)owner
));
289 else if (owner
instanceof PsiMethod
) {
290 psiMethod
= (PsiMethod
)owner
;
291 trin
= Trinity
.create(psiMethod
.getName(), psiMethod
.getParameterList().getParametersCount(), -1);
293 else if (owner
instanceof PsiVariable
) {
294 final PsiVariable variable
= (PsiVariable
)owner
;
295 for (PsiReference psiReference
: ReferencesSearch
.search(variable
, searchScope
).findAll()) {
296 final PsiElement element
= psiReference
.getElement();
297 if (element
instanceof PsiExpression
) {
298 places
.add((PsiExpression
)element
);
306 final Collection
<MethodParameterInjection
> injections
= getMethodCache().get(trin
);
307 if (injections
== null) return;
308 for (MethodParameterInjection injection
: injections
) {
309 if (injection
.isApplicable(psiMethod
)) {
310 processInjection(place
, injection
.getInjectedLanguageId(), injection
.getPrefix(), injection
.getSuffix(), processor
);
317 private MultiValuesMap
<Trinity
<String
, Integer
, Integer
>, MethodParameterInjection
> getMethodCache() {
318 if (myMethodCache
!= null && myInjectionConfiguration
.getModificationCount() == myConfigurationModificationCount
) {
319 return myMethodCache
;
321 myConfigurationModificationCount
= myInjectionConfiguration
.getModificationCount();
322 final MultiValuesMap
<Trinity
<String
, Integer
, Integer
>, MethodParameterInjection
> tmpMap
=
323 new MultiValuesMap
<Trinity
<String
, Integer
, Integer
>, MethodParameterInjection
>();
324 for (MethodParameterInjection injection
: myInjectionConfiguration
.getParameterInjections()) {
325 for (MethodParameterInjection
.MethodInfo info
: injection
.getMethodInfos()) {
326 final boolean[] flags
= info
.getParamFlags();
327 for (int i
= 0; i
< flags
.length
; i
++) {
328 if (!flags
[i
]) continue;
329 tmpMap
.put(Trinity
.create(info
.getMethodName(), flags
.length
, i
), injection
);
331 if (info
.isReturnFlag()) {
332 tmpMap
.put(Trinity
.create(info
.getMethodName(), 0, -1), injection
);
336 myMethodCache
= tmpMap
;
340 private boolean processAnnotationInjections(final PsiExpression psiExpression
, final PsiModifierListOwner annoElement
, final PairProcessor
<Language
, List
<Trinity
<PsiLanguageInjectionHost
, InjectedLanguage
, TextRange
>>> processor
) {
341 final PsiAnnotation
[] annotations
=
342 AnnotationUtilEx
.getAnnotationFrom(annoElement
, myInjectionConfiguration
.getLanguageAnnotationPair(), true);
343 if (annotations
.length
> 0) {
344 final String id
= AnnotationUtilEx
.calcAnnotationValue(annotations
, "value");
345 final String prefix
= AnnotationUtilEx
.calcAnnotationValue(annotations
, "prefix");
346 final String suffix
= AnnotationUtilEx
.calcAnnotationValue(annotations
, "suffix");
347 processInjection(psiExpression
, id
, prefix
, suffix
, processor
);
353 private void processInjection(final PsiExpression psiExpression
,
357 final PairProcessor
<Language
, List
<Trinity
<PsiLanguageInjectionHost
, InjectedLanguage
, TextRange
>>> processor
) {
358 final Ref
<Boolean
> concatFlag
= Ref
.create(isReferenceAndHasPrefix(psiExpression
));
359 for (PsiLiteralExpression literalExpression
: findLiteralExpressions(psiExpression
, myInjectionConfiguration
.isResolveReferences(), concatFlag
)) {
360 processInjectionWithContext(literalExpression
, id
, prefix
, suffix
, concatFlag
, processor
);
364 private static boolean isReferenceAndHasPrefix(final PsiExpression psiExpression
) {
365 if (!(psiExpression
instanceof PsiReferenceExpression
)) return false;
366 for (PsiElement expression
= psiExpression
, parent
= psiExpression
.getParent();
367 parent
!= null && !(parent
instanceof PsiStatement
);
368 expression
= parent
, parent
= expression
.getParent()) {
369 if (parent
instanceof PsiBinaryExpression
&& ((PsiBinaryExpression
)parent
).getROperand() == expression
) return true;
374 private static void processInjectionWithContext(final PsiLiteralExpression place
,
378 final Ref
<Boolean
> unparsable
,
379 final PairProcessor
<Language
, List
<Trinity
<PsiLanguageInjectionHost
, InjectedLanguage
, TextRange
>>> processor
) {
380 final List
<Object
> objects
= ContextComputationProcessor
.collectOperands(place
, prefix
, suffix
, unparsable
);
381 if (objects
.isEmpty()) return;
382 final List
<Trinity
<PsiLanguageInjectionHost
, InjectedLanguage
, TextRange
>> list
= new ArrayList
<Trinity
<PsiLanguageInjectionHost
, InjectedLanguage
, TextRange
>>();
383 final boolean dynamic
= !objects
.isEmpty();
384 final int len
= objects
.size();
385 for (int i
= 0; i
< len
; i
++) {
386 String curPrefix
= null;
387 Object o
= objects
.get(i
);
388 if (o
instanceof String
) {
389 curPrefix
= (String
)o
;
390 if (i
== len
- 1) return; // IDEADEV-26751
391 o
= objects
.get(++i
);
393 String curSuffix
= null;
394 PsiLanguageInjectionHost curHost
= null;
395 if (o
instanceof PsiLanguageInjectionHost
) {
396 curHost
= (PsiLanguageInjectionHost
)o
;
398 final Object next
= objects
.get(i
+ 1);
399 if (next
instanceof String
) {
401 curSuffix
= (String
)next
;
405 if (curHost
== null) unparsable
.set(Boolean
.TRUE
);
406 else list
.add(Trinity
.create(curHost
, InjectedLanguage
.create(langId
, curPrefix
, curSuffix
, dynamic
), ElementManipulators
.getManipulator(curHost
).getRangeInElement(curHost
)));
408 for (Trinity
<PsiLanguageInjectionHost
, InjectedLanguage
, TextRange
> trinity
: list
) {
409 trinity
.first
.putUserData(HAS_UNPARSABLE_FRAGMENTS
, unparsable
.get());
411 processor
.process(InjectedLanguage
.findLanguageById(langId
), list
);
414 public void disposeComponent() {
418 public String
getComponentName() {
419 return "IntelliLang.CustomLanguageInjector";
422 public void projectOpened() {
425 public void projectClosed() {
428 public void addTempInjection(PsiLanguageInjectionHost host
, InjectedLanguage selectedValue
) {
429 final SmartPointerManager manager
= SmartPointerManager
.getInstance(myProject
);
430 final SmartPsiElementPointer
<PsiLanguageInjectionHost
> pointer
= manager
.createSmartPsiElementPointer(host
);
432 synchronized (myTempPlaces
) {
433 myTempPlaces
.add(Pair
.create(pointer
, selectedValue
));
437 private static class MyLanguageInjector
implements MultiHostInjector
{
438 private CustomLanguageInjector myInjector
;
439 private static final Logger LOG
= Logger
.getInstance(CustomLanguageInjector
.class.getName());
441 MyLanguageInjector(CustomLanguageInjector injector
) {
442 myInjector
= injector
;
447 public List
<?
extends Class
<?
extends PsiElement
>> elementsToInjectIn() {
448 final THashSet
<Class
<?
extends PsiElement
>> elements
= new THashSet
<Class
<?
extends PsiElement
>>();
449 for (CustomLanguageInjectorExtension o
: Extensions
.getExtensions(CustomLanguageInjectorExtension
.EP_NAME
)) {
450 o
.elementsToInjectIn(elements
);
452 elements
.addAll(Arrays
.asList(PsiLiteralExpression
.class, XmlTag
.class, XmlAttributeValue
.class));
453 return Arrays
.<Class
<?
extends PsiElement
>>asList(elements
.toArray(new Class
[elements
.size()]));
456 public void getLanguagesToInject(@NotNull final MultiHostRegistrar registrar
, @NotNull PsiElement host
) {
457 final TreeSet
<TextRange
> ranges
= new TreeSet
<TextRange
>(RANGE_COMPARATOR
);
458 final PsiFile containingFile
= host
.getContainingFile();
459 myInjector
.getInjectedLanguage(host
, new PairProcessor
<Language
, List
<Trinity
<PsiLanguageInjectionHost
, InjectedLanguage
, TextRange
>>>() {
460 public boolean process(final Language language
, List
<Trinity
<PsiLanguageInjectionHost
, InjectedLanguage
, TextRange
>> list
) {
461 // if language isn't injected when length == 0, subsequent edits will not cause the language to be injected as well.
462 // Maybe IDEA core is caching a bit too aggressively here?
463 if (language
== null/* && (pair.second.getLength() > 0*/) {
466 for (Trinity
<PsiLanguageInjectionHost
, InjectedLanguage
, TextRange
> trinity
: list
) {
467 if (ranges
.contains(trinity
.third
.shiftRight(trinity
.first
.getTextRange().getStartOffset()))) return true;
469 boolean injectionStarted
= false;
470 for (Trinity
<PsiLanguageInjectionHost
, InjectedLanguage
, TextRange
> trinity
: list
) {
471 final PsiLanguageInjectionHost host
= trinity
.first
;
472 if (host
.getContainingFile() != containingFile
) continue;
474 final TextRange textRange
= trinity
.third
;
475 final InjectedLanguage injectedLanguage
= trinity
.second
;
476 ranges
.add(textRange
.shiftRight(host
.getTextRange().getStartOffset()));
478 if (!injectionStarted
) {
479 registrar
.startInjecting(language
);
480 injectionStarted
= true;
482 if (injectedLanguage
.isDynamic()) {
483 // Only adjust prefix/suffix if it has been computed dynamically. Otherwise some other
484 // useful cases may break. This system is far from perfect still...
485 final StringBuilder prefix
= new StringBuilder(injectedLanguage
.getPrefix());
486 final StringBuilder suffix
= new StringBuilder(injectedLanguage
.getSuffix());
487 adjustPrefixAndSuffix(getUnescapedText(host
, textRange
.substring(host
.getText())), prefix
, suffix
);
489 addPlaceSafe(registrar
, prefix
.toString(), suffix
.toString(), host
, textRange
, language
);
492 addPlaceSafe(registrar
, injectedLanguage
.getPrefix(), injectedLanguage
.getSuffix(), host
, textRange
, language
);
496 if (injectionStarted
) {
497 registrar
.doneInjecting();
500 //catch (AssertionError e) {
501 // logError(language, host, null, e);
503 //catch (Exception e) {
504 // logError(language, host, null, e);
512 private static void addPlaceSafe(MultiHostRegistrar registrar
, String prefix
, String suffix
, PsiLanguageInjectionHost host
, TextRange textRange
,
515 registrar
.addPlace(prefix
, suffix
, host
, textRange
);
517 //catch (AssertionError e) {
518 // logError(language, host, textRange, e);
520 //catch (Exception e) {
521 // logError(language, host, textRange, e);
525 private static void logError(@NotNull Language language
, @NotNull PsiElement host
, TextRange textRange
, Throwable e
) {
526 final String place
= "[" + host
.getText() + " - " + textRange
+ "]";
527 LOG
.info("Failed to inject language '" + language
.getID() + "' into '" + place
+ "'. Possibly there are overlapping injection areas.", e
);
530 private static String
getUnescapedText(final PsiElement host
, final String text
) {
531 if (host
instanceof PsiLiteralExpression
) {
532 return StringUtil
.unescapeStringCharacters(text
);
534 else if (host
instanceof XmlElement
) {
535 return XmlUtil
.unescape(text
);
542 // Avoid sticking text and prefix/suffix together in a way that it would form a single token.
543 // See http://www.jetbrains.net/jira/browse/IDEADEV-8302#action_111865
544 // This code assumes that for the injected language a single space character is a token separator
545 // that doesn't (significantly) change the semantics if added to the prefix/suffix
547 // NOTE: This does not work in all cases, such as string literals in JavaScript where a
548 // space character isn't a token separator. See also comments in IDEA-8561
549 private static void adjustPrefixAndSuffix(String text
, StringBuilder prefix
, StringBuilder suffix
) {
550 if (prefix
.length() > 0) {
551 if (!endsWithSpace(prefix
) && !startsWithSpace(text
)) {
554 else if (endsWithSpace(prefix
) && startsWithSpace(text
)) {
558 if (suffix
.length() > 0) {
559 if (text
.length() == 0) {
560 // avoid to stick whitespace from prefix and suffix together
563 else if (!startsWithSpace(suffix
) && !endsWithSpace(text
)) {
564 suffix
.insert(0, " ");
566 else if (startsWithSpace(suffix
) && endsWithSpace(text
)) {
572 private static void trim(StringBuilder string
) {
573 while (startsWithSpace(string
)) string
.deleteCharAt(0);
574 while (endsWithSpace(string
)) string
.deleteCharAt(string
.length() - 1);
577 private static boolean startsWithSpace(CharSequence sequence
) {
578 final int length
= sequence
.length();
579 return length
> 0 && sequence
.charAt(0) <= ' ';
582 private static boolean endsWithSpace(CharSequence sequence
) {
583 final int length
= sequence
.length();
584 return length
> 0 && sequence
.charAt(length
- 1) <= ' ';
588 public static CustomLanguageInjector
getInstance(Project project
) {
589 return project
.getComponent(CustomLanguageInjector
.class);
592 private static Collection
<PsiExpression
> getVariableAssignmentsInFile(final PsiVariable psiVariable
, final Ref
<Boolean
> concatenationFlag
) {
593 final TreeSet
<TextRange
> ranges
= new TreeSet
<TextRange
>(RANGE_COMPARATOR
);
594 final List
<PsiElement
> otherReferences
= new ArrayList
<PsiElement
>();
595 final List
<PsiExpression
> list
= ContainerUtil
.mapNotNull(
596 ReferencesSearch
.search(psiVariable
, new LocalSearchScope(new PsiElement
[]{psiVariable
.getContainingFile()}, null, true)).findAll(),
597 new NullableFunction
<PsiReference
, PsiExpression
>() {
598 public PsiExpression
fun(final PsiReference psiReference
) {
599 final PsiElement element
= psiReference
.getElement();
600 final PsiElement parent
= element
.getParent();
601 if (parent
instanceof PsiAssignmentExpression
) {
602 final PsiAssignmentExpression assignmentExpression
= (PsiAssignmentExpression
)parent
;
603 final IElementType operation
= assignmentExpression
.getOperationTokenType();
604 if (assignmentExpression
.getLExpression() == psiReference
) {
605 ranges
.add(assignmentExpression
.getTextRange());
606 if (JavaTokenType
.PLUSEQ
.equals(operation
)) {
607 concatenationFlag
.set(Boolean
.TRUE
);
609 return assignmentExpression
.getRExpression();
613 otherReferences
.add(element
);
618 if (!concatenationFlag
.get().booleanValue()) {
619 for (PsiElement element
: otherReferences
) {
620 if (ranges
.contains(element
.getTextRange())) {
621 concatenationFlag
.set(Boolean
.TRUE
);
626 ContainerUtil
.addIfNotNull(psiVariable
.getInitializer(), list
);