1 package com
.intellij
.psi
.util
;
3 import com
.intellij
.lang
.ASTNode
;
4 import com
.intellij
.lang
.Language
;
5 import com
.intellij
.lang
.PsiBuilder
;
6 import com
.intellij
.lang
.PsiParser
;
7 import com
.intellij
.lang
.injection
.InjectedLanguageManager
;
8 import com
.intellij
.openapi
.editor
.Editor
;
9 import com
.intellij
.openapi
.editor
.SelectionModel
;
10 import com
.intellij
.openapi
.editor
.Document
;
11 import com
.intellij
.openapi
.project
.Project
;
12 import com
.intellij
.openapi
.util
.Comparing
;
13 import com
.intellij
.openapi
.util
.Key
;
14 import com
.intellij
.openapi
.util
.TextRange
;
15 import com
.intellij
.openapi
.vfs
.VirtualFile
;
16 import com
.intellij
.psi
.*;
17 import com
.intellij
.psi
.meta
.PsiMetaData
;
18 import com
.intellij
.psi
.meta
.PsiMetaOwner
;
19 import com
.intellij
.psi
.scope
.PsiScopeProcessor
;
20 import com
.intellij
.psi
.search
.GlobalSearchScope
;
21 import com
.intellij
.psi
.search
.SearchScope
;
22 import com
.intellij
.psi
.tree
.IElementType
;
23 import com
.intellij
.injected
.editor
.DocumentWindow
;
24 import org
.jetbrains
.annotations
.NotNull
;
25 import org
.jetbrains
.annotations
.Nullable
;
28 import java
.util
.List
;
30 public class PsiUtilBase
{
31 public static final PsiElement NULL_PSI_ELEMENT
= new PsiElement() {
33 public Project
getProject() {
34 throw new PsiInvalidElementAccessException(this);
38 public Language
getLanguage() {
39 throw new IllegalAccessError();
42 public PsiManager
getManager() {
47 public PsiElement
[] getChildren() {
48 return new PsiElement
[0];
51 public PsiElement
getParent() {
56 public PsiElement
getFirstChild() {
61 public PsiElement
getLastChild() {
66 public PsiElement
getNextSibling() {
71 public PsiElement
getPrevSibling() {
75 public PsiFile
getContainingFile() {
76 throw new PsiInvalidElementAccessException(this);
79 public TextRange
getTextRange() {
83 public int getStartOffsetInParent() {
87 public int getTextLength() {
91 public PsiElement
findElementAt(int offset
) {
96 public PsiReference
findReferenceAt(int offset
) {
100 public int getTextOffset() {
104 public String
getText() {
109 public char[] textToCharArray() {
113 public PsiElement
getNavigationElement() {
117 public PsiElement
getOriginalElement() {
121 public boolean textMatches(@NotNull CharSequence text
) {
125 public boolean textMatches(@NotNull PsiElement element
) {
129 public boolean textContains(char c
) {
133 public void accept(@NotNull PsiElementVisitor visitor
) {
137 public void acceptChildren(@NotNull PsiElementVisitor visitor
) {
141 public PsiElement
copy() {
145 public PsiElement
add(@NotNull PsiElement element
) {
149 public PsiElement
addBefore(@NotNull PsiElement element
, PsiElement anchor
) {
153 public PsiElement
addAfter(@NotNull PsiElement element
, PsiElement anchor
) {
157 public void checkAdd(@NotNull PsiElement element
) {
161 public PsiElement
addRange(PsiElement first
, PsiElement last
) {
165 public PsiElement
addRangeBefore(@NotNull PsiElement first
, @NotNull PsiElement last
, PsiElement anchor
) {
169 public PsiElement
addRangeAfter(PsiElement first
, PsiElement last
, PsiElement anchor
) {
173 public void delete() {
177 public void checkDelete() {
181 public void deleteChildRange(PsiElement first
, PsiElement last
) {
185 public PsiElement
replace(@NotNull PsiElement newElement
) {
189 public boolean isValid() {
193 public boolean isWritable() {
198 public PsiReference
getReference() {
203 public PsiReference
[] getReferences() {
204 return new PsiReference
[0];
207 public <T
> T
getCopyableUserData(Key
<T
> key
) {
211 public <T
> void putCopyableUserData(Key
<T
> key
, T value
) {
215 public boolean processDeclarations(@NotNull PsiScopeProcessor processor
, @NotNull ResolveState state
, PsiElement lastParent
, @NotNull PsiElement place
) {
219 public PsiElement
getContext() {
223 public boolean isPhysical() {
228 public GlobalSearchScope
getResolveScope() {
229 return GlobalSearchScope
.EMPTY_SCOPE
;
233 public SearchScope
getUseScope() {
234 return GlobalSearchScope
.EMPTY_SCOPE
;
237 public ASTNode
getNode() {
241 public <T
> T
getUserData(Key
<T
> key
) {
245 public <T
> void putUserData(Key
<T
> key
, T value
) {
249 public Icon
getIcon(int flags
) {
253 public boolean isEquivalentTo(final PsiElement another
) {
254 return this == another
;
258 public static final PsiParser NULL_PARSER
= new PsiParser() {
260 public ASTNode
parse(IElementType root
, PsiBuilder builder
) {
261 throw new IllegalAccessError();
265 public static int getRootIndex(PsiElement root
) {
266 ASTNode node
= root
.getNode();
267 while(node
!= null && node
.getTreeParent() != null) {
268 node
= node
.getTreeParent();
270 if(node
!= null) root
= node
.getPsi();
271 final PsiFile containingFile
= root
.getContainingFile();
272 final PsiFile
[] psiRoots
= containingFile
.getPsiRoots();
273 for (int i
= 0; i
< psiRoots
.length
; i
++) {
274 if(root
== psiRoots
[i
]) return i
;
276 throw new RuntimeException("invalid element");
280 public static VirtualFile
getVirtualFile(@Nullable PsiElement element
) {
281 if (element
== null || !element
.isValid()) {
285 if (element
instanceof PsiFileSystemItem
) {
286 return ((PsiFileSystemItem
)element
).getVirtualFile();
289 final PsiFile containingFile
= element
.getContainingFile();
290 if (containingFile
== null) {
294 return containingFile
.getVirtualFile();
297 public static int compareElementsByPosition(final PsiElement element1
, final PsiElement element2
) {
298 if (element1
!= null && element2
!= null) {
299 final PsiFile psiFile1
= element1
.getContainingFile();
300 final PsiFile psiFile2
= element2
.getContainingFile();
301 if (Comparing
.equal(psiFile1
, psiFile2
)){
302 final TextRange textRange1
= element1
.getTextRange();
303 final TextRange textRange2
= element2
.getTextRange();
304 if (textRange1
!= null && textRange2
!= null) {
305 return textRange1
.getStartOffset() - textRange2
.getStartOffset();
307 } else if (psiFile1
!= null && psiFile2
!= null){
308 final String name1
= psiFile1
.getName();
309 final String name2
= psiFile2
.getName();
310 return name1
.compareToIgnoreCase(name2
);
317 public static Language
getLanguageAtOffset (@NotNull PsiFile file
, int offset
) {
318 final PsiElement elt
= file
.findElementAt(offset
);
319 if (elt
== null) return file
.getLanguage();
320 if (elt
instanceof PsiWhiteSpace
) {
321 final int decremented
= elt
.getTextRange().getStartOffset() - 1;
322 if (decremented
>= 0) {
323 return getLanguageAtOffset(file
, decremented
);
326 return findLanguageFromElement(elt
);
330 public static Language
findLanguageFromElement(final PsiElement elt
) {
331 if (elt
.getFirstChild() == null) { //is leaf
332 final PsiElement parent
= elt
.getParent();
333 if (parent
!= null) {
334 return parent
.getLanguage();
338 return elt
.getLanguage();
341 public static PsiFile
getTemplateLanguageFile(final PsiElement element
) {
342 if (element
== null) return null;
343 final PsiFile containingFile
= element
.getContainingFile();
344 if (containingFile
== null) return null;
346 final FileViewProvider viewProvider
= containingFile
.getViewProvider();
347 return viewProvider
.getPsi(viewProvider
.getBaseLanguage());
350 /** @return name for element using element structure info
353 public static String
getName(PsiElement element
) {
355 if (element
instanceof PsiMetaOwner
) {
356 final PsiMetaData data
= ((PsiMetaOwner
) element
).getMetaData();
358 name
= data
.getName(element
);
361 if (name
== null && element
instanceof PsiNamedElement
) {
362 name
= ((PsiNamedElement
) element
).getName();
367 public static boolean isUnderPsiRoot(PsiFile root
, PsiElement element
) {
368 PsiFile containingFile
= element
.getContainingFile();
369 if (containingFile
== root
) return true;
370 for (PsiFile psiRoot
: root
.getPsiRoots()) {
371 if (containingFile
== psiRoot
) return true;
373 PsiLanguageInjectionHost host
= InjectedLanguageManager
.getInstance(root
.getProject()).getInjectionHost(element
);
374 return host
!= null && isUnderPsiRoot(root
, host
);
378 public static <T
extends PsiElement
> T
getOriginalElement(@NotNull T psiElement
, final Class
<?
extends T
> elementClass
) {
379 final PsiFile psiFile
= psiElement
.getContainingFile();
380 final PsiFile originalFile
= psiFile
.getOriginalFile();
381 if (originalFile
== psiFile
) return psiElement
;
382 final TextRange range
= psiElement
.getTextRange();
383 final PsiElement element
= originalFile
.findElementAt(range
.getStartOffset());
384 final int maxLength
= range
.getLength();
385 T parent
= PsiTreeUtil
.getParentOfType(element
, elementClass
, false);
386 for (T next
= parent
;
387 next
!= null && next
.getTextLength() <= maxLength
;
388 parent
= next
, next
= PsiTreeUtil
.getParentOfType(next
, elementClass
, true)) {
394 public static Language
getLanguageInEditor(@NotNull final Editor editor
, @NotNull final Project project
) {
395 PsiFile file
= PsiDocumentManager
.getInstance(project
).getPsiFile(editor
.getDocument());
396 if (file
== null) return null;
398 final SelectionModel selectionModel
= editor
.getSelectionModel();
399 int caretOffset
= editor
.getCaretModel().getOffset();
400 int mostProbablyCorrectLanguageOffset
= caretOffset
== selectionModel
.getSelectionStart() ||
401 caretOffset
== selectionModel
.getSelectionEnd()
402 ? selectionModel
.getSelectionStart()
404 PsiElement elt
= getElementAtOffset(file
, mostProbablyCorrectLanguageOffset
);
405 Language lang
= findLanguageFromElement(elt
);
407 if (selectionModel
.hasSelection()) {
408 final Language rangeLanguage
= evaluateLanguageInRange(selectionModel
.getSelectionStart(), selectionModel
.getSelectionEnd(), file
);
409 if (rangeLanguage
== null) return file
.getLanguage();
411 lang
= rangeLanguage
;
414 return narrowLanguage(lang
, file
.getLanguage());
417 public static Language
getDialect(@NotNull PsiElement element
) {
418 return narrowLanguage(element
.getLanguage(), element
.getContainingFile().getLanguage());
421 private static Language
narrowLanguage(final Language language
, final Language candidate
) {
422 if (candidate
.isKindOf(language
)) return candidate
;
427 public static PsiFile
getPsiFileInEditor(final Editor editor
, final Project project
) {
428 final PsiFile file
= PsiDocumentManager
.getInstance(project
).getPsiFile(editor
.getDocument());
429 if (file
== null) return null;
431 final Language language
= getLanguageInEditor(editor
, project
);
432 if (language
== null) return file
;
434 if (language
== file
.getLanguage()) return file
;
436 final SelectionModel selectionModel
= editor
.getSelectionModel();
437 int caretOffset
= editor
.getCaretModel().getOffset();
438 int mostProbablyCorrectLanguageOffset
= caretOffset
== selectionModel
.getSelectionStart() ||
439 caretOffset
== selectionModel
.getSelectionEnd()
440 ? selectionModel
.getSelectionStart()
442 return getPsiFileAtOffset(file
, mostProbablyCorrectLanguageOffset
);
445 public static PsiFile
getPsiFileAtOffset(final PsiFile file
, final int offset
) {
446 PsiElement elt
= getElementAtOffset(file
, offset
);
448 assert elt
.isValid() : elt
+ "; file: "+file
+ "; isvalid: "+file
.isValid();
449 return elt
.getContainingFile();
453 public static Language
reallyEvaluateLanguageInRange(final int start
, final int end
, final PsiFile file
) {
454 Language lang
= null;
455 int curOffset
= start
;
457 PsiElement elt
= getElementAtOffset(file
, curOffset
);
459 if (!(elt
instanceof PsiWhiteSpace
)) {
460 final Language language
= findLanguageFromElement(elt
);
464 else if (lang
!= language
) {
468 int endOffset
= elt
.getTextRange().getEndOffset();
469 curOffset
= endOffset
<= curOffset ? curOffset
+ 1 : endOffset
;
471 while (curOffset
< end
);
472 return narrowLanguage(lang
, file
.getLanguage());
476 public static Language
evaluateLanguageInRange(final int start
, final int end
, final PsiFile file
) {
477 PsiElement elt
= getElementAtOffset(file
, start
);
479 TextRange selectionRange
= new TextRange(start
, end
);
480 if (!(elt
instanceof PsiFile
)) {
481 elt
= elt
.getParent();
482 TextRange range
= elt
.getTextRange();
483 assert range
!= null : "Range is null for " + elt
+ "; " + elt
.getClass();
484 while(!range
.contains(selectionRange
) && !(elt
instanceof PsiFile
)) {
485 elt
= elt
.getParent();
486 if (elt
== null) break;
487 range
= elt
.getTextRange();
488 assert range
!= null : "Range is null for " + elt
+ "; " + elt
.getClass();
492 return elt
.getLanguage();
496 return reallyEvaluateLanguageInRange(start
, end
, file
);
500 public static PsiElement
getElementAtOffset(@NotNull PsiFile file
, int offset
) {
501 PsiElement elt
= file
.findElementAt(offset
);
502 if (elt
== null && offset
> 0) {
503 elt
= file
.findElementAt(offset
- 1);
511 public static <T
extends PsiElement
> T
copyElementPreservingOriginalLinks(final T element
, final Key
<PsiElement
> originalKey
) {
512 final PsiElementVisitor originalVisitor
= new PsiRecursiveElementWalkingVisitor() {
513 public void visitElement(final PsiElement element
) {
514 element
.putCopyableUserData(originalKey
, element
);
515 super.visitElement(element
);
518 originalVisitor
.visitElement(element
);
520 final PsiElement fileCopy
= element
.copy();
522 final PsiElementVisitor copyVisitor
= new PsiRecursiveElementWalkingVisitor() {
523 public void visitElement(final PsiElement element
) {
524 final PsiElement originalElement
= element
.getCopyableUserData(originalKey
);
525 if (originalElement
!= null) {
526 originalElement
.putCopyableUserData(originalKey
, null);
527 element
.putCopyableUserData(originalKey
, null);
528 element
.putUserData(originalKey
, originalElement
);
530 super.visitElement(element
);
534 copyVisitor
.visitElement(fileCopy
);
538 public static boolean hasErrorElementChild(PsiElement element
) {
539 for (PsiElement child
= element
.getFirstChild(); child
!= null; child
= child
.getNextSibling()) {
540 if (child
instanceof PsiErrorElement
) return true;
546 public static ASTNode
getRoot(@NotNull ASTNode node
) {
547 ASTNode child
= node
;
549 final ASTNode parent
= child
.getTreeParent();
550 if (parent
== null) return child
;
556 public static int findInjectedElementOffsetInRealDocument(final PsiElement element
) {
557 final PsiFile containingFile
= element
.getContainingFile();
558 if (containingFile
== null) return -1;
559 Document document
= PsiDocumentManager
.getInstance(containingFile
.getProject()).getDocument(containingFile
);
560 if (document
instanceof DocumentWindow
&& !((DocumentWindow
)document
).isValid()) {
563 final PsiElement context
= containingFile
.getContext();
564 if (!(context
instanceof PsiLanguageInjectionHost
)) {
567 final int[] result
= new int[1];
568 ((PsiLanguageInjectionHost
)context
).processInjectedPsi(new PsiLanguageInjectionHost
.InjectedPsiVisitor() {
569 public void visit(@NotNull final PsiFile injectedPsi
, @NotNull final List
<PsiLanguageInjectionHost
.Shred
> places
) {
570 if (injectedPsi
!= containingFile
) {
573 final PsiLanguageInjectionHost
.Shred shred
= places
.get(0);
574 final TextRange textRange
= element
.getTextRange();
575 if (shred
.prefix
!= null && textRange
.getEndOffset() < shred
.prefix
.length()) {
579 final int injectedStart
= shred
.getRangeInsideHost().getStartOffset() +
580 shred
.host
.getTextRange().getStartOffset() - (shred
.prefix
!= null ? shred
.prefix
.length():0);
581 result
[0] = injectedStart
;