1 package com
.intellij
.psi
.impl
.source
.tree
.injected
;
3 import com
.intellij
.injected
.editor
.DocumentWindow
;
4 import com
.intellij
.injected
.editor
.DocumentWindowImpl
;
5 import com
.intellij
.injected
.editor
.EditorWindow
;
6 import com
.intellij
.injected
.editor
.VirtualFileWindow
;
7 import com
.intellij
.lang
.injection
.InjectedLanguageManager
;
8 import com
.intellij
.openapi
.editor
.Document
;
9 import com
.intellij
.openapi
.editor
.Editor
;
10 import com
.intellij
.openapi
.editor
.RangeMarker
;
11 import com
.intellij
.openapi
.editor
.SelectionModel
;
12 import com
.intellij
.openapi
.editor
.impl
.EditorImpl
;
13 import com
.intellij
.openapi
.fileEditor
.FileEditorManager
;
14 import com
.intellij
.openapi
.fileEditor
.OpenFileDescriptor
;
15 import com
.intellij
.openapi
.project
.Project
;
16 import com
.intellij
.openapi
.util
.*;
17 import com
.intellij
.openapi
.vfs
.VirtualFile
;
18 import com
.intellij
.psi
.*;
19 import com
.intellij
.psi
.impl
.ParameterizedCachedValueImpl
;
20 import com
.intellij
.psi
.impl
.PsiDocumentManagerImpl
;
21 import com
.intellij
.psi
.impl
.PsiManagerEx
;
22 import com
.intellij
.psi
.tree
.IElementType
;
23 import com
.intellij
.psi
.util
.CachedValueProvider
;
24 import com
.intellij
.psi
.util
.ParameterizedCachedValue
;
25 import com
.intellij
.psi
.util
.PsiModificationTracker
;
26 import com
.intellij
.util
.SmartList
;
27 import org
.jetbrains
.annotations
.NotNull
;
28 import org
.jetbrains
.annotations
.Nullable
;
30 import java
.util
.List
;
31 import java
.util
.concurrent
.CopyOnWriteArrayList
;
36 public class InjectedLanguageUtil
{
37 static final Key
<List
<Trinity
<IElementType
, PsiLanguageInjectionHost
, TextRange
>>> HIGHLIGHT_TOKENS
= Key
.create("HIGHLIGHT_TOKENS");
39 public static void forceInjectionOnElement(@NotNull final PsiElement host
) {
40 enumerate(host
, new PsiLanguageInjectionHost
.InjectedPsiVisitor() {
41 public void visit(@NotNull PsiFile injectedPsi
, @NotNull List
<PsiLanguageInjectionHost
.Shred
> places
) {
47 public static List
<Pair
<PsiElement
, TextRange
>> getInjectedPsiFiles(@NotNull final PsiElement host
) {
48 final List
<Pair
<PsiElement
, TextRange
>> result
= new SmartList
<Pair
<PsiElement
, TextRange
>>();
49 enumerate(host
, new PsiLanguageInjectionHost
.InjectedPsiVisitor() {
50 public void visit(@NotNull PsiFile injectedPsi
, @NotNull List
<PsiLanguageInjectionHost
.Shred
> places
) {
51 for (PsiLanguageInjectionHost
.Shred place
: places
) {
52 if (place
.host
== host
) {
53 result
.add(new Pair
<PsiElement
, TextRange
>(injectedPsi
, place
.getRangeInsideHost()));
58 return result
.isEmpty() ?
null : result
;
61 public static TextRange
toTextRange(RangeMarker marker
) {
62 return new ProperTextRange(marker
.getStartOffset(), marker
.getEndOffset());
65 public static List
<Trinity
<IElementType
, PsiLanguageInjectionHost
, TextRange
>> getHighlightTokens(PsiFile file
) {
66 return file
.getUserData(HIGHLIGHT_TOKENS
);
69 public static Place
getShreds(PsiFile injectedFile
) {
70 FileViewProvider viewProvider
= injectedFile
.getViewProvider();
71 if (!(viewProvider
instanceof InjectedFileViewProvider
)) return null;
72 InjectedFileViewProvider myFileViewProvider
= (InjectedFileViewProvider
)viewProvider
;
73 return myFileViewProvider
.getShreds();
76 public static void enumerate(@NotNull PsiElement host
, @NotNull PsiLanguageInjectionHost
.InjectedPsiVisitor visitor
) {
77 PsiFile containingFile
= host
.getContainingFile();
78 enumerate(host
, containingFile
, visitor
, true);
81 public static void enumerate(@NotNull PsiElement host
, @NotNull PsiFile containingFile
, @NotNull PsiLanguageInjectionHost
.InjectedPsiVisitor visitor
, boolean probeUp
) {
82 //do not inject into nonphysical files except during completion
83 if (!containingFile
.isPhysical() && containingFile
.getOriginalFile() == null) {
84 final PsiElement context
= containingFile
.getContext();
85 if (context
== null) return;
87 final PsiFile file
= context
.getContainingFile();
88 if (file
== null || !file
.isPhysical() && file
.getOriginalFile() == null) return;
90 Places places
= probeElementsUp(host
, containingFile
, probeUp
);
91 if (places
== null) return;
92 for (Place place
: places
) {
93 PsiFile injectedPsi
= place
.getInjectedPsi();
94 visitor
.visit(injectedPsi
, place
);
98 public static Editor
getEditorForInjectedLanguageNoCommit(@Nullable Editor editor
, @Nullable PsiFile file
) {
99 if (editor
== null || file
== null || editor
instanceof EditorWindow
) return editor
;
101 int offset
= editor
.getCaretModel().getOffset();
102 return getEditorForInjectedLanguageNoCommit(editor
, file
, offset
);
105 public static Editor
getEditorForInjectedLanguageNoCommit(@Nullable Editor editor
, @Nullable PsiFile file
, final int offset
) {
106 if (editor
== null || file
== null || editor
instanceof EditorWindow
) return editor
;
107 PsiFile injectedFile
= findInjectedPsiNoCommit(file
, offset
);
108 return getInjectedEditorForInjectedFile(editor
, injectedFile
);
112 public static Editor
getInjectedEditorForInjectedFile(@NotNull Editor editor
, final PsiFile injectedFile
) {
113 if (injectedFile
== null || editor
instanceof EditorWindow
) return editor
;
114 Document document
= PsiDocumentManager
.getInstance(editor
.getProject()).getDocument(injectedFile
);
115 if (!(document
instanceof DocumentWindowImpl
)) return editor
;
116 DocumentWindowImpl documentWindow
= (DocumentWindowImpl
)document
;
117 SelectionModel selectionModel
= editor
.getSelectionModel();
118 if (selectionModel
.hasSelection()) {
119 int selstart
= selectionModel
.getSelectionStart();
120 int selend
= selectionModel
.getSelectionEnd();
121 if (!documentWindow
.containsRange(selstart
, selend
)) {
122 // selection spreads out the injected editor range
126 return EditorWindow
.create(documentWindow
, (EditorImpl
)editor
, injectedFile
);
129 public static PsiFile
findInjectedPsiNoCommit(@NotNull PsiFile host
, int offset
) {
130 PsiElement injected
= findInjectedElementNoCommit(host
, offset
);
131 if (injected
!= null) {
132 return injected
.getContainingFile();
137 // consider injected elements
138 public static PsiElement
findElementAtNoCommit(@NotNull PsiFile file
, int offset
) {
139 if (!InjectedLanguageManager
.getInstance(file
.getProject()).isInjectedFragment(file
)) {
140 PsiElement injected
= findInjectedElementNoCommit(file
, offset
);
141 if (injected
!= null) {
145 //PsiElement at = file.findElementAt(offset);
146 FileViewProvider viewProvider
= file
.getViewProvider();
147 return viewProvider
.findElementAt(offset
, viewProvider
.getBaseLanguage());
150 private static final InjectedPsiCachedValueProvider INJECTED_PSI_PROVIDER
= new InjectedPsiCachedValueProvider();
151 private static final Key
<ParameterizedCachedValue
<Places
, PsiElement
>> INJECTED_PSI_KEY
= Key
.create("INJECTED_PSI");
152 private static Places
probeElementsUp(@NotNull PsiElement element
, @NotNull PsiFile hostPsiFile
, boolean probeUp
) {
153 PsiManager psiManager
= hostPsiFile
.getManager();
154 final Project project
= psiManager
.getProject();
155 InjectedLanguageManagerImpl injectedManager
= InjectedLanguageManagerImpl
.getInstanceImpl(project
);
156 if (injectedManager
== null) return null; //for tests
158 for (PsiElement current
= element
; current
!= null && current
!= hostPsiFile
; current
= current
.getParent()) {
159 if ("EL".equals(current
.getLanguage().getID())) break;
160 ParameterizedCachedValue
<Places
,PsiElement
> data
= current
.getUserData(INJECTED_PSI_KEY
);
163 places
= InjectedPsiCachedValueProvider
.doCompute(current
, injectedManager
, project
, hostPsiFile
);
164 if (places
!= null) {
165 // pollute user data only if there is injected fragment there
166 ParameterizedCachedValue
<Places
, PsiElement
> cachedValue
= psiManager
.getCachedValuesManager().createParameterizedCachedValue(INJECTED_PSI_PROVIDER
, false);
167 Document hostDocument
= hostPsiFile
.getViewProvider().getDocument();
168 CachedValueProvider
.Result
<Places
> result
= new CachedValueProvider
.Result
<Places
>(places
, PsiModificationTracker
.MODIFICATION_COUNT
, hostDocument
);
169 ((ParameterizedCachedValueImpl
<Places
, PsiElement
>)cachedValue
).setValue(result
);
170 current
.putUserData(INJECTED_PSI_KEY
, cachedValue
);
174 places
= data
.getValue(current
);
176 if (places
!= null) {
177 // check that injections found intersect with queried element
178 TextRange elementRange
= element
.getTextRange();
179 for (Place place
: places
) {
180 for (PsiLanguageInjectionHost
.Shred shred
: place
) {
181 if (shred
.host
.getTextRange().intersects(elementRange
)) {
182 assert places
.isValid();
193 public static PsiElement
findInjectedElementNoCommitWithOffset(@NotNull PsiFile file
, final int offset
) {
194 Project project
= file
.getProject();
195 if (InjectedLanguageManager
.getInstance(project
).isInjectedFragment(file
)) return null;
196 final PsiDocumentManager documentManager
= PsiDocumentManager
.getInstance(project
);
198 PsiElement element
= file
.getViewProvider().findElementAt(offset
, file
.getLanguage());
199 return element
== null ?
null : findInside(element
, file
, offset
, documentManager
);
202 public static PsiElement
findInjectedElementNoCommit(@NotNull PsiFile file
, final int offset
) {
203 PsiElement inj
= findInjectedElementNoCommitWithOffset(file
, offset
);
204 if (inj
!= null) return inj
;
206 inj
= findInjectedElementNoCommitWithOffset(file
, offset
- 1);
211 private static PsiElement
findInside(@NotNull PsiElement element
, @NotNull PsiFile file
, final int offset
, @NotNull final PsiDocumentManager documentManager
) {
212 final Ref
<PsiElement
> out
= new Ref
<PsiElement
>();
213 enumerate(element
, file
, new PsiLanguageInjectionHost
.InjectedPsiVisitor() {
214 public void visit(@NotNull PsiFile injectedPsi
, @NotNull List
<PsiLanguageInjectionHost
.Shred
> places
) {
215 for (PsiLanguageInjectionHost
.Shred place
: places
) {
216 TextRange hostRange
= place
.host
.getTextRange();
217 if (hostRange
.cutOut(place
.getRangeInsideHost()).grown(1).contains(offset
)) {
218 DocumentWindowImpl document
= (DocumentWindowImpl
)documentManager
.getCachedDocument(injectedPsi
);
219 int injectedOffset
= document
.hostToInjected(offset
);
220 PsiElement injElement
= injectedPsi
.findElementAt(injectedOffset
);
221 out
.set(injElement
== null ? injectedPsi
: injElement
);
229 private static final Key
<List
<DocumentWindow
>> INJECTED_DOCS_KEY
= Key
.create("INJECTED_DOCS_KEY");
230 private static final Key
<List
<RangeMarker
>> INJECTED_REGIONS_KEY
= Key
.create("INJECTED_REGIONS_KEY");
232 public static List
<DocumentWindow
> getCachedInjectedDocuments(@NotNull PsiFile hostPsiFile
) {
233 List
<DocumentWindow
> injected
= hostPsiFile
.getUserData(INJECTED_DOCS_KEY
);
234 if (injected
== null) {
235 injected
= ((UserDataHolderEx
)hostPsiFile
).putUserDataIfAbsent(INJECTED_DOCS_KEY
, new CopyOnWriteArrayList
<DocumentWindow
>());
240 public static void commitAllInjectedDocuments(Document hostDocument
, Project project
) {
241 List
<RangeMarker
> injected
= getCachedInjectedRegions(hostDocument
);
242 if (injected
.isEmpty()) return;
244 PsiDocumentManager documentManager
= PsiDocumentManager
.getInstance(project
);
245 PsiFile hostPsiFile
= documentManager
.getPsiFile(hostDocument
);
246 assert hostPsiFile
!= null;
247 for (RangeMarker rangeMarker
: injected
) {
248 PsiElement element
= rangeMarker
.isValid() ? hostPsiFile
.findElementAt(rangeMarker
.getStartOffset()) : null;
249 if (element
== null) {
250 injected
.remove(rangeMarker
);
253 // it is here reparse happens and old file contents replaced
254 enumerate(element
, hostPsiFile
, new PsiLanguageInjectionHost
.InjectedPsiVisitor() {
255 public void visit(@NotNull PsiFile injectedPsi
, @NotNull List
<PsiLanguageInjectionHost
.Shred
> places
) {
256 PsiDocumentManagerImpl
.checkConsistency(injectedPsi
, injectedPsi
.getViewProvider().getDocument());
260 PsiDocumentManagerImpl
.checkConsistency(hostPsiFile
, hostDocument
);
263 public static void clearCaches(PsiFile injected
) {
264 VirtualFileWindow virtualFile
= (VirtualFileWindow
)injected
.getVirtualFile();
265 PsiManagerEx psiManagerEx
= (PsiManagerEx
)injected
.getManager();
266 psiManagerEx
.getFileManager().setViewProvider((VirtualFile
)virtualFile
, null);
267 Project project
= psiManagerEx
.getProject();
268 if (!project
.isDisposed()) {
269 InjectedLanguageManagerImpl
.getInstanceImpl(project
).clearCaches(virtualFile
);
274 static List
<RangeMarker
> getCachedInjectedRegions(Document hostDocument
) {
275 List
<RangeMarker
> injectedRegions
= hostDocument
.getUserData(INJECTED_REGIONS_KEY
);
276 if (injectedRegions
== null) {
277 injectedRegions
= ((UserDataHolderEx
)hostDocument
).putUserDataIfAbsent(INJECTED_REGIONS_KEY
, new CopyOnWriteArrayList
<RangeMarker
>());
279 return injectedRegions
;
282 public static Editor
openEditorFor(PsiFile file
, Project project
) {
283 Document document
= PsiDocumentManager
.getInstance(project
).getDocument(file
);
284 // may return editor injected in current selection in the host editor, not for the file passed as argument
285 VirtualFile virtualFile
= file
.getVirtualFile();
286 if (virtualFile
== null) {
289 if (virtualFile
instanceof VirtualFileWindow
) {
290 virtualFile
= ((VirtualFileWindow
)virtualFile
).getDelegate();
292 Editor editor
= FileEditorManager
.getInstance(project
).openTextEditor(new OpenFileDescriptor(project
, virtualFile
, -1), false);
293 if (editor
== null || editor
instanceof EditorWindow
) return editor
;
294 if (document
instanceof DocumentWindowImpl
) {
295 return EditorWindow
.create((DocumentWindowImpl
)document
, (EditorImpl
)editor
, file
);
300 public static PsiFile
getTopLevelFile(PsiElement element
) {
301 PsiFile containingFile
= element
.getContainingFile();
302 Document document
= PsiDocumentManager
.getInstance(element
.getProject()).getCachedDocument(containingFile
);
303 if (document
instanceof DocumentWindow
) {
304 PsiElement host
= containingFile
.getContext();
305 if (host
!= null) containingFile
= host
.getContainingFile();
307 return containingFile
;
309 public static boolean isInInjectedLanguagePrefixSuffix(final PsiElement element
) {
310 PsiFile injectedFile
= element
.getContainingFile();
311 if (injectedFile
== null) return false;
312 Project project
= injectedFile
.getProject();
313 InjectedLanguageManager languageManager
= InjectedLanguageManager
.getInstance(project
);
314 if (!languageManager
.isInjectedFragment(injectedFile
)) return false;
315 TextRange elementRange
= element
.getTextRange();
316 List
<TextRange
> editables
= languageManager
.intersectWithAllEditableFragments(injectedFile
, elementRange
);
317 int combinedEdiablesLength
= 0;
318 for (TextRange editable
: editables
) {
319 combinedEdiablesLength
+= editable
.getLength();
322 return combinedEdiablesLength
!= elementRange
.getLength();
325 public static boolean isSelectionIsAboutToOverflowInjectedFragment(EditorWindow injectedEditor
) {
326 int selStart
= injectedEditor
.getSelectionModel().getSelectionStart();
327 int selEnd
= injectedEditor
.getSelectionModel().getSelectionEnd();
329 DocumentWindow document
= injectedEditor
.getDocument();
331 boolean isStartOverflows
= selStart
== 0;
332 if (!isStartOverflows
) {
333 int hostPrev
= document
.injectedToHost(selStart
- 1);
334 isStartOverflows
= document
.hostToInjected(hostPrev
) == selStart
;
337 boolean isEndOverflows
= selEnd
== document
.getTextLength();
338 if (!isEndOverflows
) {
339 int hostNext
= document
.injectedToHost(selEnd
+ 1);
340 isEndOverflows
= document
.hostToInjected(hostNext
) == selEnd
;
343 return isStartOverflows
&& isEndOverflows
;