intelliLang injector redesign p1
[fedora-idea.git] / plugins / IntelliLang / src / org / intellij / plugins / intelliLang / inject / CustomLanguageInjector.java
blob05585f564c11d5c021b4e74d7705f40c02d0e7c6
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;
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;
53 import java.util.*;
55 /**
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.
58 * <p/>
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.
61 * <p/>
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) {
82 myProject = project;
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) {
94 // optimization
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) {
102 it.remove();
104 else if (element == place) {
105 processor.process(pair.second.getLanguage(), Collections.singletonList(Trinity.create(element, pair.second, ElementManipulators.getManipulator(element).getRangeInElement(element))));
106 return;
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() {
121 @Override
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()) {
148 break;
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:
157 // XmlToken: "
158 // JSEmbeddedContent
159 // XmlToken "
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) {
165 return;
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()) {
181 break;
186 else {
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)) {
206 if (isArray) {
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);
216 else {
217 ContainerUtil.addIfNotNull(findFirstLiteralExpression(expression), list);
222 return 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);
233 return 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) {
247 return true;
251 return false;
254 @Nullable
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()));
267 return null;
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>();
274 places.add(place);
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);
301 continue;
303 else {
304 continue;
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);
311 return;
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;
337 return 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);
348 return true;
350 return false;
353 private void processInjection(final PsiExpression psiExpression,
354 final String id,
355 final String prefix,
356 final String suffix,
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;
371 return false;
374 private static void processInjectionWithContext(final PsiLiteralExpression place,
375 final String langId,
376 final String prefix,
377 final String suffix,
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;
397 if (i == len-2) {
398 final Object next = objects.get(i + 1);
399 if (next instanceof String) {
400 i ++;
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() {
417 @NotNull
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;
446 @NotNull
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*/) {
464 return true;
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);
491 else {
492 addPlaceSafe(registrar, injectedLanguage.getPrefix(), injectedLanguage.getSuffix(), host, textRange, language);
495 //try {
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);
507 return true;
512 private static void addPlaceSafe(MultiHostRegistrar registrar, String prefix, String suffix, PsiLanguageInjectionHost host, TextRange textRange,
513 Language language) {
514 //try {
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);
537 else {
538 return 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)) {
552 prefix.append(" ");
554 else if (endsWithSpace(prefix) && startsWithSpace(text)) {
555 trim(prefix);
558 if (suffix.length() > 0) {
559 if (text.length() == 0) {
560 // avoid to stick whitespace from prefix and suffix together
561 trim(suffix);
563 else if (!startsWithSpace(suffix) && !endsWithSpace(text)) {
564 suffix.insert(0, " ");
566 else if (startsWithSpace(suffix) && endsWithSpace(text)) {
567 trim(suffix);
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();
612 else {
613 otherReferences.add(element);
615 return null;
618 if (!concatenationFlag.get().booleanValue()) {
619 for (PsiElement element : otherReferences) {
620 if (ranges.contains(element.getTextRange())) {
621 concatenationFlag.set(Boolean.TRUE);
622 break;
626 ContainerUtil.addIfNotNull(psiVariable.getInitializer(), list);
627 return list;