2 * Copyright 2000-2009 JetBrains s.r.o.
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 com
.intellij
.psi
.impl
.source
.tree
.injected
;
19 import com
.intellij
.injected
.editor
.DocumentWindowImpl
;
20 import com
.intellij
.injected
.editor
.VirtualFileWindow
;
21 import com
.intellij
.lang
.Language
;
22 import com
.intellij
.lang
.injection
.InjectedLanguageManager
;
23 import com
.intellij
.lang
.injection
.MultiHostInjector
;
24 import com
.intellij
.lang
.injection
.MultiHostRegistrar
;
25 import com
.intellij
.openapi
.Disposable
;
26 import com
.intellij
.openapi
.editor
.Document
;
27 import com
.intellij
.openapi
.extensions
.ExtensionPoint
;
28 import com
.intellij
.openapi
.extensions
.ExtensionPointListener
;
29 import com
.intellij
.openapi
.extensions
.Extensions
;
30 import com
.intellij
.openapi
.extensions
.PluginDescriptor
;
31 import com
.intellij
.openapi
.project
.DumbAware
;
32 import com
.intellij
.openapi
.project
.DumbService
;
33 import com
.intellij
.openapi
.project
.Project
;
34 import com
.intellij
.openapi
.util
.Disposer
;
35 import com
.intellij
.openapi
.util
.Key
;
36 import com
.intellij
.openapi
.util
.ProperTextRange
;
37 import com
.intellij
.openapi
.util
.TextRange
;
38 import com
.intellij
.openapi
.vfs
.VirtualFile
;
39 import com
.intellij
.psi
.*;
40 import com
.intellij
.psi
.impl
.PsiManagerEx
;
41 import com
.intellij
.util
.ArrayUtil
;
42 import com
.intellij
.util
.containers
.ConcurrentHashMap
;
43 import org
.jetbrains
.annotations
.NonNls
;
44 import org
.jetbrains
.annotations
.NotNull
;
45 import org
.jetbrains
.annotations
.Nullable
;
46 import org
.jetbrains
.annotations
.TestOnly
;
49 import java
.util
.concurrent
.ConcurrentMap
;
50 import java
.util
.concurrent
.atomic
.AtomicReference
;
55 public class InjectedLanguageManagerImpl
extends InjectedLanguageManager
{
56 private final Project myProject
;
57 private final DumbService myDumbService
;
58 private final AtomicReference
<MultiHostInjector
> myPsiManagerRegisteredInjectorsAdapter
= new AtomicReference
<MultiHostInjector
>();
60 public static InjectedLanguageManagerImpl
getInstanceImpl(Project project
) {
61 return (InjectedLanguageManagerImpl
)InjectedLanguageManager
.getInstance(project
);
64 public InjectedLanguageManagerImpl(Project project
, DumbService dumbService
) {
66 myDumbService
= dumbService
;
68 final ExtensionPoint
<MultiHostInjector
> multiPoint
= Extensions
.getArea(project
).getExtensionPoint(MULTIHOST_INJECTOR_EP_NAME
);
69 multiPoint
.addExtensionPointListener(new ExtensionPointListener
<MultiHostInjector
>() {
70 public void extensionAdded(MultiHostInjector injector
, @Nullable PluginDescriptor pluginDescriptor
) {
71 registerMultiHostInjector(injector
);
74 public void extensionRemoved(MultiHostInjector injector
, @Nullable PluginDescriptor pluginDescriptor
) {
75 unregisterMultiHostInjector(injector
);
78 final ExtensionPointListener
<LanguageInjector
> myListener
= new ExtensionPointListener
<LanguageInjector
>() {
79 public void extensionAdded(LanguageInjector extension
, @Nullable PluginDescriptor pluginDescriptor
) {
80 psiManagerInjectorsChanged();
83 public void extensionRemoved(LanguageInjector extension
, @Nullable PluginDescriptor pluginDescriptor
) {
84 psiManagerInjectorsChanged();
87 final ExtensionPoint
<LanguageInjector
> psiManagerPoint
= Extensions
.getRootArea().getExtensionPoint(LanguageInjector
.EXTENSION_POINT_NAME
);
88 psiManagerPoint
.addExtensionPointListener(myListener
);
89 Disposer
.register(project
, new Disposable() {
90 public void dispose() {
91 psiManagerPoint
.removeExtensionPointListener(myListener
);
96 public void projectOpened() {
99 public void projectClosed() {
102 public void psiManagerInjectorsChanged() {
103 PsiManagerEx psiManager
= (PsiManagerEx
)PsiManager
.getInstance(myProject
);
104 List
<?
extends LanguageInjector
> injectors
= psiManager
.getLanguageInjectors();
105 LanguageInjector
[] extensions
= Extensions
.getExtensions(LanguageInjector
.EXTENSION_POINT_NAME
);
106 if (injectors
.isEmpty() && extensions
.length
== 0) {
107 MultiHostInjector prev
= myPsiManagerRegisteredInjectorsAdapter
.getAndSet(null);
109 unregisterMultiHostInjector(prev
);
113 PsiManagerRegisteredInjectorsAdapter adapter
= new PsiManagerRegisteredInjectorsAdapter(psiManager
);
114 if (myPsiManagerRegisteredInjectorsAdapter
.compareAndSet(null, adapter
)) {
115 registerMultiHostInjector(adapter
);
120 public PsiLanguageInjectionHost
getInjectionHost(@NotNull PsiElement element
) {
121 final PsiFile file
= element
.getContainingFile();
122 final VirtualFile virtualFile
= file
== null ?
null : file
.getVirtualFile();
123 if (virtualFile
instanceof VirtualFileWindow
) {
124 PsiElement host
= file
.getContext();
126 return (PsiLanguageInjectionHost
)host
;
133 public TextRange
injectedToHost(@NotNull PsiElement element
, @NotNull TextRange textRange
) {
134 ProperTextRange
.assertProperRange(textRange
);
135 PsiFile file
= element
.getContainingFile();
136 if (file
== null) return textRange
;
137 Document document
= PsiDocumentManager
.getInstance(element
.getProject()).getCachedDocument(file
);
138 if (!(document
instanceof DocumentWindowImpl
)) return textRange
;
139 DocumentWindowImpl documentWindow
= (DocumentWindowImpl
)document
;
140 return documentWindow
.injectedToHost(textRange
);
142 public int injectedToHost(@NotNull PsiElement element
, int offset
) {
143 PsiFile file
= element
.getContainingFile();
144 if (file
== null) return offset
;
145 Document document
= PsiDocumentManager
.getInstance(element
.getProject()).getCachedDocument(file
);
146 if (!(document
instanceof DocumentWindowImpl
)) return offset
;
147 DocumentWindowImpl documentWindow
= (DocumentWindowImpl
)document
;
148 return documentWindow
.injectedToHost(offset
);
151 private final ConcurrentMap
<Class
, MultiHostInjector
[]> injectors
= new ConcurrentHashMap
<Class
, MultiHostInjector
[]>();
152 private final ClassMapCachingNulls
<MultiHostInjector
> cachedInjectors
= new ClassMapCachingNulls
<MultiHostInjector
>(injectors
, new MultiHostInjector
[0]);
154 public void registerMultiHostInjector(@NotNull MultiHostInjector injector
) {
155 for (Class
<?
extends PsiElement
> place
: injector
.elementsToInjectIn()) {
157 MultiHostInjector
[] injectors
= this.injectors
.get(place
);
158 if (injectors
== null) {
159 if (this.injectors
.putIfAbsent(place
, new MultiHostInjector
[]{injector
}) == null) break;
162 MultiHostInjector
[] newInfos
= ArrayUtil
.append(injectors
, injector
);
163 if (this.injectors
.replace(place
, injectors
, newInfos
)) break;
167 cachedInjectors
.clearCache();
170 public boolean unregisterMultiHostInjector(@NotNull MultiHostInjector injector
) {
171 boolean removed
= false;
172 Iterator
<Map
.Entry
<Class
,MultiHostInjector
[]>> iterator
= injectors
.entrySet().iterator();
173 while (iterator
.hasNext()) {
174 Map
.Entry
<Class
,MultiHostInjector
[]> entry
= iterator
.next();
175 MultiHostInjector
[] infos
= entry
.getValue();
176 int i
= ArrayUtil
.find(infos
, injector
);
178 MultiHostInjector
[] newInfos
= ArrayUtil
.remove(infos
, i
);
179 if (newInfos
.length
== 0) {
183 injectors
.put(entry
.getKey(), newInfos
);
188 cachedInjectors
.clearCache();
193 static final Key
<String
> UNESCAPED_TEXT
= Key
.create("INJECTED_UNESCAPED_TEXT");
194 public String
getUnescapedText(@NotNull final PsiElement injectedNode
) {
195 final StringBuilder text
= new StringBuilder(injectedNode
.getTextLength());
196 // gather text from (patched) leaves
197 injectedNode
.accept(new PsiRecursiveElementWalkingVisitor() {
199 public void visitElement(PsiElement element
) {
200 String unescaped
= element
.getUserData(UNESCAPED_TEXT
);
201 if (unescaped
!= null) {
202 text
.append(unescaped
);
205 if (element
.getFirstChild() == null) {
206 text
.append(element
.getText());
209 super.visitElement(element
);
212 return text
.toString();
216 * intersection may spread over several injected fragments
217 * @param rangeToEdit range in encoded(raw) PSI
218 * @return list of ranges in encoded (raw) PSI
220 @SuppressWarnings({"ConstantConditions"})
222 public List
<TextRange
> intersectWithAllEditableFragments(@NotNull PsiFile injectedPsi
, @NotNull TextRange rangeToEdit
) {
223 Place shreds
= InjectedLanguageUtil
.getShreds(injectedPsi
);
224 if (shreds
== null) return Collections
.emptyList();
225 Object result
= null; // optimization: TextRange or ArrayList
228 for (PsiLanguageInjectionHost
.Shred shred
: shreds
) {
229 TextRange encodedRange
= TextRange
.from(offset
+ shred
.prefix
.length(), shred
.getRangeInsideHost().getLength());
230 TextRange intersection
= encodedRange
.intersection(rangeToEdit
);
231 if (intersection
!= null) {
234 result
= intersection
;
236 else if (count
== 2) {
237 TextRange range
= (TextRange
)result
;
238 if (range
.isEmpty()) {
239 result
= intersection
;
242 else if (intersection
.isEmpty()) {
246 List
<TextRange
> list
= new ArrayList
<TextRange
>();
248 list
.add(intersection
);
252 else if (intersection
.isEmpty()) {
256 ((List
<TextRange
>)result
).add(intersection
);
259 offset
+= shred
.prefix
.length() + shred
.getRangeInsideHost().getLength() + shred
.suffix
.length();
261 return count
== 0 ? Collections
.<TextRange
>emptyList() : count
== 1 ? Collections
.singletonList((TextRange
)result
) : (List
<TextRange
>)result
;
264 public boolean isInjectedFragment(final PsiFile file
) {
265 return file
.getViewProvider() instanceof InjectedFileViewProvider
;
269 public PsiElement
findInjectedElementAt(@NotNull PsiFile hostFile
, int hostDocumentOffset
) {
270 return InjectedLanguageUtil
.findInjectedElementNoCommitWithOffset(hostFile
, hostDocumentOffset
);
273 private final Map
<Class
,MultiHostInjector
[]> myInjectorsClone
= new HashMap
<Class
, MultiHostInjector
[]>();
275 public void pushInjectors() {
277 assert myInjectorsClone
.isEmpty() : myInjectorsClone
;
280 myInjectorsClone
.clear();
282 myInjectorsClone
.putAll(injectors
);
285 public void checkInjectorsAreDisposed() {
287 for (Map
.Entry
<Class
, MultiHostInjector
[]> entry
: injectors
.entrySet()) {
288 Class key
= entry
.getKey();
289 MultiHostInjector
[] oldInjectors
= myInjectorsClone
.get(key
);
290 for (MultiHostInjector injector
: entry
.getValue()) {
291 if (!ArrayUtil
.contains(injector
, oldInjectors
)) {
292 throw new AssertionError("Injector was not disposed: " + key
+ " -> " + injector
);
298 myInjectorsClone
.clear();
302 public interface InjProcessor
{
303 boolean process(PsiElement element
, MultiHostInjector injector
);
305 public void processInPlaceInjectorsFor(@NotNull PsiElement element
, @NotNull InjProcessor processor
) {
306 final boolean dumb
= myDumbService
.isDumb();
307 MultiHostInjector
[] infos
= cachedInjectors
.get(element
.getClass());
309 for (MultiHostInjector injector
: infos
) {
310 if (dumb
&& !(injector
instanceof DumbAware
)) {
314 if (!processor
.process(element
, injector
)) return;
321 public String
getComponentName() {
322 return "InjectedLanguageManager";
325 public void initComponent() {
328 public void disposeComponent() {
331 private static class PsiManagerRegisteredInjectorsAdapter
implements MultiHostInjector
{
332 private final PsiManagerEx myPsiManager
;
334 private PsiManagerRegisteredInjectorsAdapter(PsiManagerEx psiManager
) {
335 myPsiManager
= psiManager
;
338 public void getLanguagesToInject(@NotNull final MultiHostRegistrar injectionPlacesRegistrar
, @NotNull PsiElement context
) {
339 final PsiLanguageInjectionHost host
= (PsiLanguageInjectionHost
)context
;
340 InjectedLanguagePlaces placesRegistrar
= new InjectedLanguagePlaces() {
341 public void addPlace(@NotNull Language language
, @NotNull TextRange rangeInsideHost
, @NonNls @Nullable String prefix
, @NonNls @Nullable String suffix
) {
342 ProperTextRange
.assertProperRange(rangeInsideHost
);
343 injectionPlacesRegistrar
344 .startInjecting(language
)
345 .addPlace(prefix
, suffix
, host
, rangeInsideHost
)
349 for (LanguageInjector injector
: myPsiManager
.getLanguageInjectors()) {
350 injector
.getLanguagesToInject(host
, placesRegistrar
);
352 for (LanguageInjector injector
: Extensions
.getExtensions(LanguageInjector
.EXTENSION_POINT_NAME
)) {
353 injector
.getLanguagesToInject(host
, placesRegistrar
);
358 public List
<?
extends Class
<?
extends PsiElement
>> elementsToInjectIn() {
359 return Arrays
.asList(PsiLanguageInjectionHost
.class);