From fd11bd29ddd945fe69e8697bb4c0fe7c926fa70c Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Fri, 22 Nov 2013 20:55:02 +0400 Subject: [PATCH 1/3] Support for temporary and configuration-based IntelliLang injections in Python (PY-10983, PY-10721) --- .../META-INF/intellilang-python-support.xml | 2 + .../intelliLang/PyConfigurationInjector.java | 33 +++++++++++++++++ .../PyLanguageInjectionSupport.java | 35 ------------------ .../intelliLang/PyTemporaryInjector.java | 30 +++++++++++++++ .../python/codeInsight/PyInjectionUtil.java | 18 ++++++--- .../python/codeInsight/PyInjectorBase.java | 37 +++++++++++++++++++ .../regexp/PythonRegexpInjector.java | 2 +- 7 files changed, 116 insertions(+), 41 deletions(-) create mode 100644 python/IntelliLang-python/src/com/jetbrains/python/intelliLang/PyConfigurationInjector.java create mode 100644 python/IntelliLang-python/src/com/jetbrains/python/intelliLang/PyTemporaryInjector.java create mode 100644 python/src/com/jetbrains/python/codeInsight/PyInjectorBase.java diff --git a/python/IntelliLang-python/src/META-INF/intellilang-python-support.xml b/python/IntelliLang-python/src/META-INF/intellilang-python-support.xml index 2ffa9f7c2325..624d146a0378 100644 --- a/python/IntelliLang-python/src/META-INF/intellilang-python-support.xml +++ b/python/IntelliLang-python/src/META-INF/intellilang-python-support.xml @@ -7,5 +7,7 @@ + + \ No newline at end of file diff --git a/python/IntelliLang-python/src/com/jetbrains/python/intelliLang/PyConfigurationInjector.java b/python/IntelliLang-python/src/com/jetbrains/python/intelliLang/PyConfigurationInjector.java new file mode 100644 index 000000000000..f672f5d17ee6 --- /dev/null +++ b/python/IntelliLang-python/src/com/jetbrains/python/intelliLang/PyConfigurationInjector.java @@ -0,0 +1,33 @@ +package com.jetbrains.python.intelliLang; + +import com.intellij.lang.Language; +import com.intellij.psi.PsiElement; +import com.jetbrains.python.codeInsight.PyInjectorBase; +import org.intellij.plugins.intelliLang.Configuration; +import org.intellij.plugins.intelliLang.inject.InjectedLanguage; +import org.intellij.plugins.intelliLang.inject.InjectorUtils; +import org.intellij.plugins.intelliLang.inject.LanguageInjectionSupport; +import org.intellij.plugins.intelliLang.inject.config.BaseInjection; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author vlan + */ +public class PyConfigurationInjector extends PyInjectorBase { + @Nullable + @Override + public Language getInjectedLanguage(@NotNull PsiElement context) { + for (LanguageInjectionSupport support : InjectorUtils.getActiveInjectionSupports()) { + if (support instanceof PyLanguageInjectionSupport) { + final Configuration configuration = Configuration.getInstance(); + for (BaseInjection injection : configuration.getInjections(support.getId())) { + if (injection.acceptsPsiElement(context)) { + return InjectedLanguage.findLanguageById(injection.getInjectedLanguageId()); + } + } + } + } + return null; + } +} diff --git a/python/IntelliLang-python/src/com/jetbrains/python/intelliLang/PyLanguageInjectionSupport.java b/python/IntelliLang-python/src/com/jetbrains/python/intelliLang/PyLanguageInjectionSupport.java index 5a91db5ce994..43db1f20d814 100644 --- a/python/IntelliLang-python/src/com/jetbrains/python/intelliLang/PyLanguageInjectionSupport.java +++ b/python/IntelliLang-python/src/com/jetbrains/python/intelliLang/PyLanguageInjectionSupport.java @@ -15,22 +15,14 @@ */ package com.jetbrains.python.intelliLang; -import com.intellij.lang.Language; -import com.intellij.openapi.util.TextRange; -import com.intellij.psi.PsiElement; import com.intellij.psi.PsiLanguageInjectionHost; import com.jetbrains.python.patterns.PythonPatterns; import com.jetbrains.python.psi.PyElement; -import com.jetbrains.python.psi.PyStringLiteralExpression; import org.intellij.plugins.intelliLang.inject.AbstractLanguageInjectionSupport; -import org.intellij.plugins.intelliLang.inject.config.BaseInjection; -import org.jdom.Element; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.List; - /** * @author yole */ @@ -54,33 +46,6 @@ public class PyLanguageInjectionSupport extends AbstractLanguageInjectionSupport return host instanceof PyElement; } - @Override - public boolean useDefaultInjector(PsiLanguageInjectionHost host) { - return true; - } - - @Override - public BaseInjection createInjection(Element element) { - // This is how DefaultLanguageInjector gets its injection ranges - return new BaseInjection(getId()) { - @NotNull - @Override - public List getInjectedArea(PsiElement element) { - if (element instanceof PyStringLiteralExpression) { - return ((PyStringLiteralExpression)element).getStringValueTextRanges(); - } - return super.getInjectedArea(element); - } - }; - } - - @Override - public boolean addInjectionInPlace(Language language, PsiLanguageInjectionHost psiElement) { - // XXX: Disable temporary injections via intention actions for Python elements, since TemporaryPlacesInjector cannot handle elements - // with multiple injection text ranges (PY-10691) - return true; - } - @Nullable @Override public String getHelpId() { diff --git a/python/IntelliLang-python/src/com/jetbrains/python/intelliLang/PyTemporaryInjector.java b/python/IntelliLang-python/src/com/jetbrains/python/intelliLang/PyTemporaryInjector.java new file mode 100644 index 000000000000..d88905d623cf --- /dev/null +++ b/python/IntelliLang-python/src/com/jetbrains/python/intelliLang/PyTemporaryInjector.java @@ -0,0 +1,30 @@ +package com.jetbrains.python.intelliLang; + +import com.intellij.lang.Language; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiLanguageInjectionHost; +import com.jetbrains.python.codeInsight.PyInjectorBase; +import org.intellij.plugins.intelliLang.inject.InjectedLanguage; +import org.intellij.plugins.intelliLang.inject.TemporaryPlacesRegistry; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author vlan + */ +public class PyTemporaryInjector extends PyInjectorBase { + @Nullable + @Override + public Language getInjectedLanguage(@NotNull PsiElement context) { + final TemporaryPlacesRegistry registry = TemporaryPlacesRegistry.getInstance(context.getProject()); + if (context instanceof PsiLanguageInjectionHost) { + final PsiFile file = context.getContainingFile(); + final InjectedLanguage injectedLanguage = registry.getLanguageFor((PsiLanguageInjectionHost)context, file); + if (injectedLanguage != null) { + return injectedLanguage.getLanguage(); + } + } + return null; + } +} diff --git a/python/src/com/jetbrains/python/codeInsight/PyInjectionUtil.java b/python/src/com/jetbrains/python/codeInsight/PyInjectionUtil.java index d68e6c61c47f..d0a49fe810d7 100644 --- a/python/src/com/jetbrains/python/codeInsight/PyInjectionUtil.java +++ b/python/src/com/jetbrains/python/codeInsight/PyInjectionUtil.java @@ -23,6 +23,7 @@ import com.jetbrains.python.psi.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Arrays; import java.util.List; import static com.jetbrains.python.inspections.PyStringFormatParser.*; @@ -31,15 +32,22 @@ import static com.jetbrains.python.inspections.PyStringFormatParser.*; * @author vlan */ public class PyInjectionUtil { + public static final List> ELEMENTS_TO_INJECT_IN = + Arrays.asList(PyStringLiteralExpression.class, PyParenthesizedExpression.class, PyBinaryExpression.class, PyCallExpression.class); + private PyInjectionUtil() {} /** - * Returns true if the element is the largest expression that represents a string literal, possibly with concatenation, parentheses, - * or formatting. + * Returns the largest expression in the specified context that represents a string literal suitable for language injection, possibly + * with concatenation, parentheses, or formatting. */ - public static boolean isLargestStringLiteral(@NotNull PsiElement element) { - final PsiElement parent = element.getParent(); - return isStringLiteralPart(element) && (parent == null || !isStringLiteralPart(parent)); + @Nullable + public static PsiElement getLargestStringLiteral(@NotNull PsiElement context) { + PsiElement element = null; + for (PsiElement current = context; current != null && isStringLiteralPart(current); current = current.getParent()) { + element = current; + } + return element; } /** diff --git a/python/src/com/jetbrains/python/codeInsight/PyInjectorBase.java b/python/src/com/jetbrains/python/codeInsight/PyInjectorBase.java new file mode 100644 index 000000000000..48b62368af3d --- /dev/null +++ b/python/src/com/jetbrains/python/codeInsight/PyInjectorBase.java @@ -0,0 +1,37 @@ +package com.jetbrains.python.codeInsight; + +import com.intellij.lang.Language; +import com.intellij.lang.injection.MultiHostInjector; +import com.intellij.lang.injection.MultiHostRegistrar; +import com.intellij.psi.PsiElement; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +/** + * @author vlan + */ +public abstract class PyInjectorBase implements MultiHostInjector { + @Override + public void getLanguagesToInject(@NotNull MultiHostRegistrar registrar, @NotNull PsiElement context) { + final Language language = getInjectedLanguage(context); + if (language != null) { + final PsiElement element = PyInjectionUtil.getLargestStringLiteral(context); + if (element != null) { + registrar.startInjecting(language); + PyInjectionUtil.registerStringLiteralInjection(element, registrar); + registrar.doneInjecting(); + } + } + } + + @NotNull + @Override + public List> elementsToInjectIn() { + return PyInjectionUtil.ELEMENTS_TO_INJECT_IN; + } + + @Nullable + public abstract Language getInjectedLanguage(@NotNull PsiElement context); +} diff --git a/python/src/com/jetbrains/python/codeInsight/regexp/PythonRegexpInjector.java b/python/src/com/jetbrains/python/codeInsight/regexp/PythonRegexpInjector.java index 39c9aff38410..bb23c475a576 100644 --- a/python/src/com/jetbrains/python/codeInsight/regexp/PythonRegexpInjector.java +++ b/python/src/com/jetbrains/python/codeInsight/regexp/PythonRegexpInjector.java @@ -67,7 +67,7 @@ public class PythonRegexpInjector implements MultiHostInjector { @Override public void getLanguagesToInject(@NotNull MultiHostRegistrar registrar, @NotNull PsiElement context) { final PsiElement contextParent = context.getParent(); - if (PyInjectionUtil.isLargestStringLiteral(context) && contextParent instanceof PyArgumentList) { + if (PyInjectionUtil.getLargestStringLiteral(context) == context && contextParent instanceof PyArgumentList) { final PyExpression[] args = ((PyArgumentList)contextParent).getArguments(); int index = ArrayUtil.indexOf(args, context); PyCallExpression call = PsiTreeUtil.getParentOfType(context, PyCallExpression.class); From 67c086f0bc53dfb87331cbf1b7b4190985e7bb48 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Fri, 22 Nov 2013 21:05:44 +0400 Subject: [PATCH 2/3] Fixed SQL select IntelliLang pattern (PY-10926) --- python/IntelliLang-python/src/pyInjections.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/IntelliLang-python/src/pyInjections.xml b/python/IntelliLang-python/src/pyInjections.xml index e9ae34e1a348..a75015e02441 100644 --- a/python/IntelliLang-python/src/pyInjections.xml +++ b/python/IntelliLang-python/src/pyInjections.xml @@ -9,6 +9,6 @@ "SQL select/delete/insert/update/create" - + From f4a5f9aba64e87b21740d948c4ac6a6414fbca77 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Fri, 22 Nov 2013 21:08:41 +0400 Subject: [PATCH 3/3] Don't inject any language into percent-based Python formatting even without formatting operator (PY-10771) --- .../python/codeInsight/PyInjectionUtil.java | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/python/src/com/jetbrains/python/codeInsight/PyInjectionUtil.java b/python/src/com/jetbrains/python/codeInsight/PyInjectionUtil.java index d0a49fe810d7..7ce44b5531c9 100644 --- a/python/src/com/jetbrains/python/codeInsight/PyInjectionUtil.java +++ b/python/src/com/jetbrains/python/codeInsight/PyInjectionUtil.java @@ -55,7 +55,7 @@ public class PyInjectionUtil { * string concatenations or formatting. */ public static void registerStringLiteralInjection(@NotNull PsiElement element, @NotNull MultiHostRegistrar registrar) { - processStringLiteral(element, registrar, "", "", Formatting.NONE); + processStringLiteral(element, registrar, "", "", Formatting.PERCENT); } private static boolean isStringLiteralPart(@NotNull PsiElement element) { @@ -108,9 +108,25 @@ public class PyInjectionUtil { final FormatStringChunk chunk = chunks.get(i); if (chunk instanceof ConstantChunk) { final int nextIndex = i + 1; - final String chunkPrefix = i == 1 && chunks.get(0) instanceof SubstitutionChunk ? missingValue : ""; - final String chunkSuffix = nextIndex < chunks.size() && - chunks.get(nextIndex) instanceof SubstitutionChunk ? missingValue : ""; + final String chunkPrefix; + if (i == 1 && chunks.get(0) instanceof SubstitutionChunk) { + chunkPrefix = missingValue; + } + else if (i == 0) { + chunkPrefix = prefix; + } else { + chunkPrefix = ""; + } + final String chunkSuffix; + if (nextIndex < chunks.size() && chunks.get(nextIndex) instanceof SubstitutionChunk) { + chunkSuffix = missingValue; + } + else if (nextIndex == chunks.size()) { + chunkSuffix = suffix; + } + else { + chunkSuffix = ""; + } final TextRange chunkRange = chunk.getTextRange().shiftRight(range.getStartOffset()); registrar.addPlace(chunkPrefix, chunkSuffix, expr, chunkRange); }