revert
[fedora-idea.git] / lang-impl / src / com / intellij / psi / impl / source / tree / injected / InjectedLanguageUtil.java
blob5510d7328b40312d33492789a45ab92ed3afd587
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;
33 /**
34 * @author cdr
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) {
43 });
46 @Nullable
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()));
57 });
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);
111 @NotNull
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
123 return editor;
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();
134 return null;
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) {
142 return injected;
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);
161 Places places;
162 if (data == null) {
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);
173 else {
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();
183 return places;
188 if (!probeUp) break;
190 return null;
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;
205 if (offset != 0) {
206 inj = findInjectedElementNoCommitWithOffset(file, offset - 1);
208 return inj;
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);
225 }, true);
226 return out.get();
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");
231 @NotNull
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>());
237 return injected;
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);
251 continue;
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());
258 }, true);
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) {
287 return 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);
297 return editor;
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;