blob: e9b8da7f7d203a1ec1b8ad26b172fc7e094a945b [file] [log] [blame]
/*
* Copyright 2000-2014 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.
*/
/*
* Created by IntelliJ IDEA.
* User: cdr
* Date: Sep 4, 2007
* Time: 7:17:07 PM
*/
package com.intellij.psi.impl.source.tree.injected;
import com.intellij.lang.ASTNode;
import com.intellij.lang.Language;
import com.intellij.lang.StdLanguages;
import com.intellij.lang.injection.ConcatenationAwareInjector;
import com.intellij.lang.injection.InjectedLanguageManager;
import com.intellij.lang.injection.MultiHostInjector;
import com.intellij.lang.injection.MultiHostRegistrar;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.extensions.ExtensionPoint;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.ProperTextRange;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.tree.PsiCommentImpl;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.*;
import com.intellij.util.ArrayUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.TestOnly;
import java.util.Arrays;
import java.util.List;
public class MyTestInjector {
private final PsiManager myPsiManager;
@TestOnly
public MyTestInjector(PsiManager psiManager) {
myPsiManager = psiManager;
}
public void injectAll(Disposable parent) {
injectVariousStuffEverywhere(parent, myPsiManager);
Project project = myPsiManager.getProject();
Language ql = Language.findLanguageByID("JPAQL");
Language js = Language.findLanguageByID("JavaScript");
registerForStringVarInitializer(parent, project, ql, "ql", null, null);
registerForStringVarInitializer(parent, project, ql, "qlPrefixed", "xxx", null);
registerForStringVarInitializer(parent, project, js, "js", null, null);
registerForStringVarInitializer(parent, project, js, "jsSeparated", " + ", " + 'separator'");
registerForStringVarInitializer(parent, project, js, "jsBrokenPrefix", "xx ", "");
registerForStringVarInitializer(parent, project, Language.findLanguageByID("Oracle"), "oracle", null, null);
registerForParameterValue(parent, project, Language.findLanguageByID("Groovy"), "groovy");
}
private static void registerForParameterValue(Disposable parent, final Project project, final Language language, final String paramName) {
if (language == null) return;
final ConcatenationAwareInjector injector = new ConcatenationAwareInjector() {
@Override
public void getLanguagesToInject(@NotNull MultiHostRegistrar injectionPlacesRegistrar, @NotNull PsiElement... operands) {
PsiElement operand = operands[0];
if (!(operand instanceof PsiLiteralExpression)) return;
if (!(operand.getParent() instanceof PsiExpressionList)) return;
PsiExpressionList expressionList = (PsiExpressionList)operand.getParent();
int i = ArrayUtil.indexOf(expressionList.getExpressions(), operand);
if (!(operand.getParent().getParent() instanceof PsiMethodCallExpression)) return;
PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)operand.getParent().getParent();
PsiMethod method = methodCallExpression.resolveMethod();
if (method == null) return;
PsiParameter[] parameters = method.getParameterList().getParameters();
if (i>=parameters.length) return;
PsiParameter parameter = parameters[i];
if (!paramName.equals(parameter.getName())) return;
TextRange textRange = textRangeToInject((PsiLanguageInjectionHost)operand);
injectionPlacesRegistrar.startInjecting(language)
.addPlace(null, null, (PsiLanguageInjectionHost)operand, textRange)
.doneInjecting();
}
};
JavaConcatenationInjectorManager.getInstance(project).registerConcatenationInjector(injector);
Disposer.register(parent, new Disposable() {
@Override
public void dispose() {
boolean b = JavaConcatenationInjectorManager.getInstance(project).unregisterConcatenationInjector(injector);
assert b;
}
});
}
private static void registerForStringVarInitializer(@NotNull Disposable parent,
@NotNull final Project project,
final Language language,
@NotNull @NonNls final String varName,
@NonNls final String prefix,
@NonNls final String suffix) {
if (language == null) return;
final ConcatenationAwareInjector injector = new ConcatenationAwareInjector() {
@Override
public void getLanguagesToInject(@NotNull MultiHostRegistrar injectionPlacesRegistrar, @NotNull PsiElement... operands) {
PsiVariable variable = PsiTreeUtil.getParentOfType(operands[0], PsiVariable.class);
if (variable == null) return;
if (!varName.equals(variable.getName())) return;
if (!(operands[0] instanceof PsiLiteralExpression)) return;
boolean started = false;
String prefixFromPrev="";
for (int i = 0; i < operands.length; i++) {
PsiElement operand = operands[i];
if (!(operand instanceof PsiLiteralExpression)) {
continue;
}
Object value = ((PsiLiteralExpression)operand).getValue();
if (!(value instanceof String)) {
prefixFromPrev += value;
continue;
}
TextRange textRange = textRangeToInject((PsiLanguageInjectionHost)operand);
if (!started) {
injectionPlacesRegistrar.startInjecting(language);
started = true;
}
injectionPlacesRegistrar.addPlace(prefixFromPrev + (i == 0 ? "" : prefix==null?"":prefix), i == operands.length - 1 ? null : suffix, (PsiLanguageInjectionHost)operand, textRange);
prefixFromPrev = "";
}
if (started) {
injectionPlacesRegistrar.doneInjecting();
}
}
};
JavaConcatenationInjectorManager.getInstance(project).registerConcatenationInjector(injector);
Disposer.register(parent, new Disposable() {
@Override
public void dispose() {
boolean b = JavaConcatenationInjectorManager.getInstance(project).unregisterConcatenationInjector(injector);
assert b;
}
});
}
private static void injectVariousStuffEverywhere(Disposable parent, final PsiManager psiManager) {
final Language ql = Language.findLanguageByID("JPAQL");
final Language js = Language.findLanguageByID("JavaScript");
final Language html = Language.findLanguageByID("HTML");
if (ql == null || js == null) return;
final Language ecma4 = Language.findLanguageByID("ECMA Script Level 4");
final MultiHostInjector myMultiHostInjector = new MultiHostInjector() {
@Override
public void getLanguagesToInject(@NotNull MultiHostRegistrar registrar, @NotNull PsiElement context) {
XmlAttributeValue value = (XmlAttributeValue)context;
PsiElement parent = value.getParent();
if (parent instanceof XmlAttribute) {
@NonNls String attrName = ((XmlAttribute)parent).getLocalName();
if ("jsInBraces".equals(attrName)) {
registrar.startInjecting(js);
String text = value.getText();
int index = 0;
while (text.indexOf('{', index) != -1) {
int lbrace = text.indexOf('{', index);
int rbrace = text.indexOf('}', index);
registrar.addPlace("", "", (PsiLanguageInjectionHost)value, new TextRange(lbrace + 1, rbrace));
index = rbrace + 1;
}
registrar.doneInjecting();
}
}
}
@Override
@NotNull
public List<? extends Class<? extends PsiElement>> elementsToInjectIn() {
return Arrays.asList(XmlAttributeValue.class);
}
};
InjectedLanguageManager.getInstance(psiManager.getProject()).registerMultiHostInjector(myMultiHostInjector);
Disposer.register(parent, new Disposable() {
@Override
public void dispose() {
boolean b = InjectedLanguageManager.getInstance(psiManager.getProject()).unregisterMultiHostInjector(myMultiHostInjector);
assert b;
}
});
final LanguageInjector myInjector = new LanguageInjector() {
@Override
public void getLanguagesToInject(@NotNull PsiLanguageInjectionHost host, @NotNull InjectedLanguagePlaces placesToInject) {
if (host instanceof XmlAttributeValue) {
XmlAttributeValue value = (XmlAttributeValue)host;
PsiElement parent = value.getParent();
if (parent instanceof XmlAttribute) {
@NonNls String attrName = ((XmlAttribute)parent).getLocalName();
if ("ql".equals(attrName)) {
inject(host, placesToInject, ql);
return;
}
if ("js".equals(attrName)) {
inject(host, placesToInject, js);
return;
}
if ("jsprefix".equals(attrName)) {
inject(host, placesToInject, js, "function foo(doc, window){", "}");
return;
}
}
}
if (host instanceof XmlText) {
// inject to xml tags named 'ql'
final XmlText xmlText = (XmlText)host;
XmlTag tag = xmlText.getParentTag();
if (tag == null) return;
if ("ql".equals(tag.getLocalName())) {
inject(host, placesToInject, ql);
return;
}
if ("js".equals(tag.getLocalName())) {
inject(host, placesToInject, js);
return;
}
if ("htmlInject".equals(tag.getLocalName())) {
inject(host, placesToInject, html);
return;
}
if (ecma4 != null && "ecma4".equals(tag.getLocalName())) {
inject(host, placesToInject, ecma4);
return;
}
if ("jsprefix".equals(tag.getLocalName())) {
inject(host, placesToInject, js, "function foo(doc, window){", "}");
return;
}
if ("jsInHash".equals(tag.getLocalName())) {
String text = xmlText.getText();
if (text.contains("#")) {
int start = text.indexOf('#');
int end = text.lastIndexOf('#');
if (start != end && start != -1) {
placesToInject.addPlace(js, new TextRange(start + 1, end), null, null);
return;
}
}
}
}
if (host instanceof PsiComment && ((PsiComment)host).getTokenType() == JavaTokenType.C_STYLE_COMMENT) {
/* {{{
* js code
* js code
* }}}
*/
String text = host.getText();
String prefix = "/*\n * {{{\n";
String suffix = " }}}\n */";
if (text.startsWith(prefix) && text.endsWith(suffix)) {
String s = StringUtil.trimEnd(StringUtil.trimStart(text, prefix), suffix);
int off = 0;
while (!s.isEmpty()) {
String t = s.trim();
if (t.startsWith("*")) t = t.substring(1).trim();
int i = s.length() - t.length();
off += i;
int endOfLine = t.indexOf('\n');
if (endOfLine == -1) endOfLine = t.length();
placesToInject.addPlace(js, TextRange.from(prefix.length() + off, endOfLine), "", "\n");
off += endOfLine;
s = s.substring(i+endOfLine);
}
return;
}
}
if (host instanceof PsiCommentImpl) {
String text = host.getText();
if (text.startsWith("/*--{") && text.endsWith("}--*/")) {
TextRange textRange = new TextRange(4, text.length()-4);
if (!(host.getParent()instanceof PsiMethod)) return;
PsiMethod method = (PsiMethod)host.getParent();
if (!method.hasModifierProperty(PsiModifier.NATIVE) || !method.hasModifierProperty(PsiModifier.PUBLIC)) return;
String paramList = "";
for (PsiParameter parameter : method.getParameterList().getParameters()) {
if (!paramList.isEmpty()) paramList += ",";
paramList += parameter.getName();
}
@NonNls String header = "function " + method.getName() + "("+paramList+") {";
Language gwt = Language.findLanguageByID("GWT JavaScript");
placesToInject.addPlace(gwt, textRange, header, "}");
return;
}
PsiElement parent = host.getParent();
if (parent instanceof PsiMethod && ((PsiMethod)parent).getName().equals("xml")) {
placesToInject.addPlace(StdLanguages.XML, new TextRange(2,host.getTextLength()-2), null,null);
return;
}
}
// inject to all string literal initializers of variables named 'ql'
if (host instanceof PsiLiteralExpression && ((PsiLiteralExpression)host).getValue() instanceof String) {
PsiVariable variable = PsiTreeUtil.getParentOfType(host, PsiVariable.class);
if (variable == null) return;
if (host.getParent() instanceof PsiPolyadicExpression) return;
if ("ql".equals(variable.getName())) {
placesToInject.addPlace(ql, textRangeToInject(host), null, null);
}
if ("xml".equals(variable.getName())) {
placesToInject.addPlace(StdLanguages.XML, textRangeToInject(host), null, null);
}
if ("js".equals(variable.getName())) { // with prefix/suffix
placesToInject.addPlace(js, textRangeToInject(host), "function foo(doc,window) {", "}");
}
if ("lang".equals(variable.getName())) {
// various lang depending on field "languageID" content
PsiClass aClass = PsiTreeUtil.getParentOfType(variable, PsiClass.class);
aClass = aClass.findInnerClassByName("Language", false);
String text = aClass.getInitializers()[0].getBody().getFirstBodyElement().getNextSibling().getText().substring(2);
Language language = Language.findLanguageByID(text);
if (language != null) {
placesToInject.addPlace(language, textRangeToInject(host), "", "");
}
}
}
}
};
final ExtensionPoint<LanguageInjector> extensionPoint = Extensions.getRootArea().getExtensionPoint(LanguageInjector.EXTENSION_POINT_NAME);
extensionPoint.registerExtension(myInjector);
Disposer.register(parent, new Disposable() {
@Override
public void dispose() {
extensionPoint.unregisterExtension(myInjector);
}
});
}
private static void inject(final PsiLanguageInjectionHost host, final InjectedLanguagePlaces placesToInject, final Language language) {
inject(host, placesToInject, language, null, null);
}
private static void inject(final PsiLanguageInjectionHost host, final InjectedLanguagePlaces placesToInject, final Language language, @NonNls String prefix, String suffix) {
TextRange insideQuotes = textRangeToInject(host);
placesToInject.addPlace(language, insideQuotes, prefix, suffix);
}
public static TextRange textRangeToInject(PsiLanguageInjectionHost host) {
ASTNode[] children = ((ASTNode)host).getChildren(null);
TextRange insideQuotes = new ProperTextRange(0, host.getTextLength());
if (children.length > 1 && children[0].getElementType() == XmlTokenType.XML_ATTRIBUTE_VALUE_START_DELIMITER) {
insideQuotes = new ProperTextRange(children[1].getTextRange().getStartOffset() - host.getTextRange().getStartOffset(), insideQuotes.getEndOffset());
}
if (children.length > 1 && children[children.length-1].getElementType() == XmlTokenType.XML_ATTRIBUTE_VALUE_END_DELIMITER) {
insideQuotes = new ProperTextRange(insideQuotes.getStartOffset(), children[children.length-2].getTextRange().getEndOffset() - host.getTextRange().getStartOffset());
}
if (host instanceof PsiLiteralExpression) {
insideQuotes = new ProperTextRange(1, host.getTextLength()-1);
}
return insideQuotes;
}
}