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.
16 package org
.intellij
.plugins
.intelliLang
.inject
;
18 import com
.intellij
.codeInsight
.intention
.IntentionAction
;
19 import com
.intellij
.ide
.DataManager
;
20 import com
.intellij
.lang
.Language
;
21 import com
.intellij
.lang
.StdLanguages
;
22 import com
.intellij
.openapi
.application
.Result
;
23 import com
.intellij
.openapi
.command
.WriteCommandAction
;
24 import com
.intellij
.openapi
.editor
.Editor
;
25 import com
.intellij
.openapi
.fileTypes
.FileType
;
26 import com
.intellij
.openapi
.options
.Configurable
;
27 import com
.intellij
.openapi
.project
.Project
;
28 import com
.intellij
.openapi
.ui
.popup
.JBPopupFactory
;
29 import com
.intellij
.openapi
.ui
.popup
.ListPopup
;
30 import com
.intellij
.openapi
.ui
.popup
.PopupStep
;
31 import com
.intellij
.openapi
.ui
.popup
.util
.BaseListPopupStep
;
32 import com
.intellij
.openapi
.util
.Comparing
;
33 import com
.intellij
.openapi
.util
.Pair
;
34 import com
.intellij
.openapi
.util
.TextRange
;
35 import com
.intellij
.openapi
.vfs
.VirtualFile
;
36 import com
.intellij
.psi
.*;
37 import com
.intellij
.psi
.codeStyle
.JavaCodeStyleManager
;
38 import com
.intellij
.psi
.util
.PsiTreeUtil
;
39 import com
.intellij
.psi
.util
.PsiUtil
;
40 import com
.intellij
.psi
.xml
.XmlAttribute
;
41 import com
.intellij
.psi
.xml
.XmlAttributeValue
;
42 import com
.intellij
.psi
.xml
.XmlTag
;
43 import com
.intellij
.psi
.xml
.XmlText
;
44 import com
.intellij
.util
.FileContentUtil
;
45 import com
.intellij
.util
.IncorrectOperationException
;
46 import com
.intellij
.util
.Processor
;
47 import com
.intellij
.util
.ui
.EmptyIcon
;
48 import org
.intellij
.plugins
.intelliLang
.Configuration
;
49 import org
.intellij
.plugins
.intelliLang
.inject
.config
.MethodParameterInjection
;
50 import org
.intellij
.plugins
.intelliLang
.inject
.config
.XmlAttributeInjection
;
51 import org
.intellij
.plugins
.intelliLang
.inject
.config
.XmlTagInjection
;
52 import org
.intellij
.plugins
.intelliLang
.inject
.config
.ui
.configurables
.MethodParameterInjectionConfigurable
;
53 import org
.intellij
.plugins
.intelliLang
.inject
.config
.ui
.configurables
.XmlAttributeInjectionConfigurable
;
54 import org
.intellij
.plugins
.intelliLang
.inject
.config
.ui
.configurables
.XmlTagInjectionConfigurable
;
55 import org
.jetbrains
.annotations
.NotNull
;
56 import org
.jetbrains
.annotations
.Nullable
;
61 public class InjectLanguageAction
implements IntentionAction
{
63 public String
getText() {
64 return "Inject Language";
68 public String
getFamilyName() {
69 return "Inject Language";
72 public boolean isAvailable(@NotNull Project project
, Editor editor
, PsiFile file
) {
73 final PsiLanguageInjectionHost host
= findInjectionHost(editor
, file
);
77 final List
<Pair
<PsiElement
, TextRange
>> injectedPsi
= host
.getInjectedPsi();
78 if (injectedPsi
== null || injectedPsi
.isEmpty()) {
85 protected static PsiLanguageInjectionHost
findInjectionHost(Editor editor
, PsiFile file
) {
86 final int offset
= editor
.getCaretModel().getOffset();
87 final PsiLanguageInjectionHost host
=
88 PsiTreeUtil
.getParentOfType(file
.findElementAt(offset
), PsiLanguageInjectionHost
.class, false, true);
92 if (host
instanceof PsiLiteralExpression
) {
93 final PsiType type
= ((PsiLiteralExpression
)host
).getType();
94 return type
== null || !type
.equalsToText("java.lang.String") ?
null : host
;
96 else if (host
instanceof XmlAttributeValue
) {
97 final PsiElement p
= host
.getParent();
98 if (p
instanceof XmlAttribute
) {
99 final String s
= ((XmlAttribute
)p
).getName();
100 return s
.equals("xmlns") || s
.startsWith("xmlns:") ?
null : host
;
103 else if (host
instanceof XmlText
) {
104 final XmlTag tag
= ((XmlText
)host
).getParentTag();
105 return tag
== null || tag
.getValue().getTextElements().length
> 1 || tag
.getSubTags().length
> 0 ?
null : host
;
107 // unknown injection host
111 public void invoke(@NotNull final Project project
, final Editor editor
, final PsiFile file
) throws IncorrectOperationException
{
112 final PsiLanguageInjectionHost host
= findInjectionHost(editor
, file
);
114 doChooseLanguageToInject(new Processor
<String
>() {
115 public boolean process(final String languageId
) {
116 if (!(host
instanceof XmlAttributeValue
&& doInjectInAttributeValue(project
, (XmlAttributeValue
)host
, languageId
) ||
117 host
instanceof XmlText
&& doInjectInXmlText(project
, (XmlText
)host
, languageId
) ||
118 host
.getLanguage() == StdLanguages
.JAVA
&& doInjectInJava(project
, host
, languageId
))) {
119 CustomLanguageInjector
.getInstance(project
).addTempInjection(host
, InjectedLanguage
.create(languageId
));
121 FileContentUtil
.reparseFiles(project
, Collections
.<VirtualFile
>emptyList(), true);
127 private static boolean doInjectInJava(final Project project
, final PsiElement host
, final String languageId
) {
128 PsiElement target
= host
;
129 PsiElement parent
= target
.getParent();
130 for (; parent
!= null; target
= parent
, parent
= target
.getParent()) {
131 if (parent
instanceof PsiBinaryExpression
) continue;
132 if (parent
instanceof PsiParenthesizedExpression
) continue;
133 if (parent
instanceof PsiConditionalExpression
&& ((PsiConditionalExpression
)parent
).getCondition() != target
) continue;
136 if (parent
instanceof PsiReturnStatement
||
137 parent
instanceof PsiMethod
||
138 parent
instanceof PsiNameValuePair
) {
139 return doInjectInJavaMethod(project
, findPsiMethod(parent
), -1, languageId
);
141 else if (parent
instanceof PsiExpressionList
&& parent
.getParent() instanceof PsiMethodCallExpression
) {
142 return doInjectInJavaMethod(project
, findPsiMethod(parent
), findParameterIndex(target
, (PsiExpressionList
)parent
), languageId
);
144 else if (parent
instanceof PsiAssignmentExpression
) {
145 final PsiExpression psiExpression
= ((PsiAssignmentExpression
)parent
).getLExpression();
146 if (psiExpression
instanceof PsiReferenceExpression
) {
147 final PsiElement element
= ((PsiReferenceExpression
)psiExpression
).resolve();
148 if (element
!= null) {
149 return doInjectInJava(project
, element
, languageId
);
153 else if (parent
instanceof PsiVariable
) {
154 if (doAddLanguageAnnotation(project
, (PsiModifierListOwner
)parent
, languageId
)) return true;
159 private static boolean doAddLanguageAnnotation(final Project project
, final PsiModifierListOwner modifierListOwner
,
160 final String languageId
) {
161 if (modifierListOwner
.getModifierList() == null || !PsiUtil
.getLanguageLevel(modifierListOwner
).hasEnumKeywordAndAutoboxing()) return false;
162 new WriteCommandAction(project
, modifierListOwner
.getContainingFile()) {
163 protected void run(final Result result
) throws Throwable
{
164 final String annotationName
= org
.intellij
.lang
.annotations
.Language
.class.getName();
165 final PsiAnnotation annotation
= JavaPsiFacade
.getInstance(project
).getElementFactory()
166 .createAnnotationFromText("@" + annotationName
+ "(\"" + languageId
+ "\")", modifierListOwner
);
167 final PsiModifierList list
= modifierListOwner
.getModifierList();
169 final PsiAnnotation existingAnnotation
= list
.findAnnotation(annotationName
);
170 if (existingAnnotation
!= null) existingAnnotation
.replace(annotation
);
171 else list
.addAfter(annotation
, null);
172 JavaCodeStyleManager
.getInstance(getProject()).shortenClassReferences(list
);
178 private static boolean doInjectInJavaMethod(final Project project
, final PsiMethod psiMethod
, final int parameterIndex
,
179 final String languageId
) {
180 if (psiMethod
== null) return false;
181 if (parameterIndex
< -1) return false;
182 if (parameterIndex
>= psiMethod
.getParameterList().getParametersCount()) return false;
183 final PsiModifierList methodModifiers
= psiMethod
.getModifierList();
184 if (methodModifiers
.hasModifierProperty(PsiModifier
.PRIVATE
) || methodModifiers
.hasModifierProperty(PsiModifier
.PACKAGE_LOCAL
)) {
185 return doAddLanguageAnnotation(project
, parameterIndex
>0? psiMethod
.getParameterList().getParameters()[parameterIndex
- 1] : psiMethod
, languageId
);
187 final PsiClass containingClass
= psiMethod
.getContainingClass();
188 assert containingClass
!= null;
189 final PsiModifierList classModifiers
= containingClass
.getModifierList();
190 if (classModifiers
!= null && (classModifiers
.hasModifierProperty(PsiModifier
.PRIVATE
) || classModifiers
.hasModifierProperty(PsiModifier
.PACKAGE_LOCAL
))) {
191 return doAddLanguageAnnotation(project
, parameterIndex
>0? psiMethod
.getParameterList().getParameters()[parameterIndex
- 1] : psiMethod
, languageId
);
194 final String className
= containingClass
.getQualifiedName();
195 assert className
!= null;
196 final MethodParameterInjection injection
= new MethodParameterInjection();
197 injection
.setInjectedLanguageId(languageId
);
198 injection
.setClassName(className
);
199 injection
.setApplyInHierarchy(true);
200 final MethodParameterInjection
.MethodInfo info
= MethodParameterInjection
.createMethodInfo(psiMethod
);
201 if (parameterIndex
< 0) info
.setReturnFlag(true);
202 else info
.getParamFlags()[parameterIndex
] = true;
203 injection
.setMethodInfos(Collections
.singletonList(info
));
204 doEditInjection(project
, injection
);
208 private static int findParameterIndex(final PsiElement target
, final PsiExpressionList parent
) {
209 final int idx
= Arrays
.asList(parent
.getExpressions()).indexOf(target
);
210 return idx
< 0?
-2 : idx
;
214 private static PsiMethod
findPsiMethod(final PsiElement parent
) {
215 if (parent
instanceof PsiNameValuePair
) {
216 final PsiAnnotation annotation
= PsiTreeUtil
.getParentOfType(parent
, PsiAnnotation
.class);
217 if (annotation
!= null) {
218 final PsiJavaCodeReferenceElement referenceElement
= annotation
.getNameReferenceElement();
219 if (referenceElement
!= null) {
220 PsiElement resolved
= referenceElement
.resolve();
221 if (resolved
!= null) {
222 PsiMethod
[] methods
= ((PsiClass
)resolved
).findMethodsByName(((PsiNameValuePair
)parent
).getName(), false);
223 if (methods
.length
== 1) {
230 final PsiMethod first
;
231 if (parent
.getParent() instanceof PsiMethodCallExpression
) {
232 first
= ((PsiMethodCallExpression
)parent
.getParent()).resolveMethod();
235 first
= PsiTreeUtil
.getParentOfType(parent
, PsiMethod
.class, false);
237 if (first
== null || first
.getContainingClass() == null) return null;
238 final LinkedList
<PsiMethod
> methods
= new LinkedList
<PsiMethod
>();
240 while (!methods
.isEmpty()) {
241 final PsiMethod method
= methods
.removeFirst();
242 final PsiClass psiClass
= method
.getContainingClass();
243 if (psiClass
!= null && psiClass
.getQualifiedName() != null) {
247 methods
.addAll(Arrays
.asList(method
.findSuperMethods()));
253 private static boolean doInjectInXmlText(final Project project
, final XmlText host
, final String languageId
) {
254 final XmlTag tag
= host
.getParentTag();
256 final XmlTagInjection injection
= new XmlTagInjection();
257 injection
.setInjectedLanguageId(languageId
);
258 injection
.setTagName(tag
.getLocalName());
259 injection
.setTagNamespace(tag
.getNamespace());
260 doEditInjection(project
, injection
);
266 private static boolean doInjectInAttributeValue(final Project project
, final XmlAttributeValue host
, final String languageId
) {
267 final XmlAttribute attribute
= PsiTreeUtil
.getParentOfType(host
, XmlAttribute
.class, true);
268 final XmlTag tag
= attribute
== null?
null : attribute
.getParent();
270 final XmlAttributeInjection injection
= new XmlAttributeInjection();
271 injection
.setInjectedLanguageId(languageId
);
272 injection
.setAttributeName(attribute
.getLocalName());
273 injection
.setAttributeNamespace(attribute
.getNamespace());
274 injection
.setTagName(tag
.getLocalName());
275 injection
.setTagNamespace(tag
.getNamespace());
276 doEditInjection(project
, injection
);
282 private static boolean doChooseLanguageToInject(final Processor
<String
> onChosen
) {
283 final String
[] langIds
= InjectedLanguage
.getAvailableLanguageIDs();
284 Arrays
.sort(langIds
);
286 final Map
<String
, List
<String
>> map
= new LinkedHashMap
<String
, List
<String
>>();
287 buildLanguageTree(langIds
, map
);
289 final BaseListPopupStep
<String
> step
= new MyPopupStep(map
, new ArrayList
<String
>(map
.keySet()), onChosen
);
291 final ListPopup listPopup
= JBPopupFactory
.getInstance().createListPopup(step
);
292 listPopup
.showInBestPositionFor(DataManager
.getInstance().getDataContext());
296 private static void doEditInjection(final Project project
, final XmlAttributeInjection injection
) {
297 final Configuration configuration
= Configuration
.getInstance();
298 final XmlAttributeInjection existing
= configuration
.findExistingInjection(injection
);
299 if (doEditConfigurable(project
, new XmlAttributeInjectionConfigurable(existing
== null ? injection
: existing
, null, project
))) {
300 if (existing
== null) {
301 configuration
.getAttributeInjections().add(injection
);
302 configuration
.configurationModified();
307 private static void doEditInjection(final Project project
, final XmlTagInjection injection
) {
308 final Configuration configuration
= Configuration
.getInstance();
309 final XmlTagInjection existing
= configuration
.findExistingInjection(injection
);
310 if (doEditConfigurable(project
, new XmlTagInjectionConfigurable(existing
== null? injection
: existing
, null, project
))) {
311 if (existing
== null) {
312 configuration
.getTagInjections().add(injection
);
313 configuration
.configurationModified();
318 private static void doEditInjection(final Project project
, final MethodParameterInjection injection
) {
319 final Configuration configuration
= Configuration
.getInstance();
320 final MethodParameterInjection existing
= configuration
.findExistingInjection(injection
);
321 if (existing
!= null) {
322 // merge method infos
323 boolean found
= false;
324 final MethodParameterInjection
.MethodInfo curInfo
= injection
.getMethodInfos().iterator().next();
325 for (MethodParameterInjection
.MethodInfo info
: existing
.getMethodInfos()) {
326 if (Comparing
.equal(info
.getMethodSignature(), curInfo
.getMethodSignature())) {
328 final boolean[] flags
= curInfo
.getParamFlags();
329 for (int i
= 0; i
< flags
.length
; i
++) {
331 info
.getParamFlags()[i
] = true;
334 if (!info
.isReturnFlag() && curInfo
.isReturnFlag()) info
.setReturnFlag(true);
338 final ArrayList
<MethodParameterInjection
.MethodInfo
> methodInfos
= new ArrayList
<MethodParameterInjection
.MethodInfo
>(existing
.getMethodInfos());
339 methodInfos
.add(curInfo
);
340 existing
.setMethodInfos(methodInfos
);
343 if (doEditConfigurable(project
, new MethodParameterInjectionConfigurable(existing
== null? injection
: existing
, null, project
))) {
344 if (existing
== null) {
345 configuration
.getParameterInjections().add(injection
);
347 configuration
.configurationModified();
352 private static boolean doEditConfigurable(final Project project
, final Configurable configurable
) {
353 return true; //ShowSettingsUtil.getInstance().editConfigurable(project, configurable);
356 private static void buildLanguageTree(String
[] langIds
, Map
<String
, List
<String
>> map
) {
357 for (final String id
: langIds
) {
358 if (!map
.containsKey(id
)) {
359 map
.put(id
, new ArrayList
<String
>());
364 public boolean startInWriteAction() {
368 private static class MyPopupStep
extends BaseListPopupStep
<String
> {
369 private final Map
<String
, List
<String
>> myMap
;
370 private final Processor
<String
> myFinalStepProcessor
;
372 public MyPopupStep(final Map
<String
, List
<String
>> map
, final List
<String
> values
, final Processor
<String
> finalStepProcessor
) {
373 super("Choose Language", values
);
375 myFinalStepProcessor
= finalStepProcessor
;
379 public PopupStep
onChosen(final String selectedValue
, boolean finalChoice
) {
381 myFinalStepProcessor
.process(selectedValue
);
384 return new MyPopupStep(myMap
, myMap
.get(selectedValue
), myFinalStepProcessor
);
388 public boolean hasSubstep(String selectedValue
) {
389 return myMap
.containsKey(selectedValue
) && !myMap
.get(selectedValue
).isEmpty();
393 public Icon
getIconFor(String aValue
) {
394 final Language language
= InjectedLanguage
.findLanguageById(aValue
);
395 assert language
!= null;
396 final FileType ft
= language
.getAssociatedFileType();
397 return ft
!= null ? ft
.getIcon() : new EmptyIcon(16);
402 public String
getTextFor(String value
) {
403 final Language language
= InjectedLanguage
.findLanguageById(value
);
404 assert language
!= null;
405 final FileType ft
= language
.getAssociatedFileType();
406 return value
+ (ft
!= null ?
" ("+ft
.getDescription()+")" : "");