blob: f5b10ccf1b202e5490a55c24fbbd61b332e87541 [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.psi.util;
import com.intellij.ide.DataManager;
import com.intellij.lang.ASTNode;
import com.intellij.lang.Language;
import com.intellij.lang.injection.InjectedLanguageManager;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.SelectionModel;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.TextEditor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.AsyncResult;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VFileProperty;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileSystem;
import com.intellij.psi.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.*;
public class PsiUtilBase extends PsiUtilCore {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.util.PsiUtilBase");
public static final Comparator<Language> LANGUAGE_COMPARATOR = new Comparator<Language>() {
@Override
public int compare(Language o1, Language o2) {
return o1.getID().compareTo(o2.getID());
}
};
public static int getRootIndex(PsiElement root) {
ASTNode node = root.getNode();
while(node != null && node.getTreeParent() != null) {
node = node.getTreeParent();
}
if(node != null) root = node.getPsi();
final PsiFile containingFile = root.getContainingFile();
FileViewProvider provider = containingFile.getViewProvider();
Set<Language> languages = provider.getLanguages();
if (languages.size() == 1) {
return 0;
}
List<Language> array = new ArrayList<Language>(languages);
Collections.sort(array, LANGUAGE_COMPARATOR);
for (int i = 0; i < array.size(); i++) {
Language language = array.get(i);
if (provider.getPsi(language) == containingFile) return i;
}
throw new RuntimeException("Cannot find root for: "+root);
}
@NotNull
public static Language getLanguageAtOffset (@NotNull PsiFile file, int offset) {
final PsiElement elt = file.findElementAt(offset);
if (elt == null) return file.getLanguage();
if (elt instanceof PsiWhiteSpace) {
final int decremented = elt.getTextRange().getStartOffset() - 1;
if (decremented >= 0) {
return getLanguageAtOffset(file, decremented);
}
}
return findLanguageFromElement(elt);
}
@NotNull
public static Language findLanguageFromElement(final PsiElement elt) {
if (elt.getFirstChild() == null) { //is leaf
final PsiElement parent = elt.getParent();
if (parent != null) {
return parent.getLanguage();
}
}
return elt.getLanguage();
}
public static boolean isUnderPsiRoot(PsiFile root, PsiElement element) {
PsiFile containingFile = element.getContainingFile();
if (containingFile == root) return true;
for (PsiFile psiRoot : root.getPsiRoots()) {
if (containingFile == psiRoot) return true;
}
PsiLanguageInjectionHost host = InjectedLanguageManager.getInstance(root.getProject()).getInjectionHost(element);
return host != null && isUnderPsiRoot(root, host);
}
@Nullable
public static Language getLanguageInEditor(@NotNull final Editor editor, @NotNull final Project project) {
PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
if (file == null) return null;
final SelectionModel selectionModel = editor.getSelectionModel();
int caretOffset = editor.getCaretModel().getOffset();
int mostProbablyCorrectLanguageOffset = caretOffset == selectionModel.getSelectionStart() ||
caretOffset == selectionModel.getSelectionEnd()
? selectionModel.getSelectionStart()
: caretOffset;
PsiElement elt = getElementAtOffset(file, mostProbablyCorrectLanguageOffset);
Language lang = findLanguageFromElement(elt);
if (selectionModel.hasSelection()) {
final Language rangeLanguage = evaluateLanguageInRange(selectionModel.getSelectionStart(), selectionModel.getSelectionEnd(), file);
if (rangeLanguage == null) return file.getLanguage();
lang = rangeLanguage;
}
return narrowLanguage(lang, file.getLanguage());
}
@Nullable
public static PsiElement getElementAtCaret(@NotNull Editor editor) {
Project project = editor.getProject();
if (project == null) return null;
PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
return file == null ? null : file.findElementAt(editor.getCaretModel().getOffset());
}
@Nullable
public static PsiFile getPsiFileInEditor(@NotNull final Editor editor, @NotNull final Project project) {
final PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
if (file == null) return null;
final Language language = getLanguageInEditor(editor, project);
if (language == null) return file;
if (language == file.getLanguage()) return file;
final SelectionModel selectionModel = editor.getSelectionModel();
int caretOffset = editor.getCaretModel().getOffset();
int mostProbablyCorrectLanguageOffset = caretOffset == selectionModel.getSelectionStart() ||
caretOffset == selectionModel.getSelectionEnd()
? selectionModel.getSelectionStart()
: caretOffset;
return getPsiFileAtOffset(file, mostProbablyCorrectLanguageOffset);
}
public static PsiFile getPsiFileAtOffset(final PsiFile file, final int offset) {
PsiElement elt = getElementAtOffset(file, offset);
assert elt.isValid() : elt + "; file: "+file + "; isvalid: "+file.isValid();
return elt.getContainingFile();
}
@Nullable
public static Language reallyEvaluateLanguageInRange(final int start, final int end, final PsiFile file) {
Language lang = null;
int curOffset = start;
do {
PsiElement elt = getElementAtOffset(file, curOffset);
if (!(elt instanceof PsiWhiteSpace)) {
final Language language = findLanguageFromElement(elt);
if (lang == null) {
lang = language;
}
else if (lang != language) {
return null;
}
}
TextRange range = elt.getTextRange();
if (range == null) {
LOG.error("Null range for element " + elt + " of " + elt.getClass() + " in file " + file + " at offset " + curOffset);
return file.getLanguage();
}
int endOffset = range.getEndOffset();
curOffset = endOffset <= curOffset ? curOffset + 1 : endOffset;
}
while (curOffset < end);
return narrowLanguage(lang, file.getLanguage());
}
@Nullable
public static Language evaluateLanguageInRange(final int start, final int end, final PsiFile file) {
PsiElement elt = getElementAtOffset(file, start);
TextRange selectionRange = new TextRange(start, end);
if (!(elt instanceof PsiFile)) {
elt = elt.getParent();
TextRange range = elt.getTextRange();
assert range != null : "Range is null for " + elt + "; " + elt.getClass();
while(!range.contains(selectionRange) && !(elt instanceof PsiFile)) {
elt = elt.getParent();
if (elt == null) break;
range = elt.getTextRange();
assert range != null : "Range is null for " + elt + "; " + elt.getClass();
}
if (elt != null) {
return elt.getLanguage();
}
}
return reallyEvaluateLanguageInRange(start, end, file);
}
@NotNull
public static ASTNode getRoot(@NotNull ASTNode node) {
ASTNode child = node;
do {
final ASTNode parent = child.getTreeParent();
if (parent == null) return child;
child = parent;
}
while (true);
}
/**
* Tries to find editor for the given element.
* <p/>
* There are at least two approaches to achieve the target. Current method is intended to encapsulate both of them:
* <pre>
* <ul>
* <li>target editor works with a real file that remains at file system;</li>
* <li>target editor works with a virtual file;</li>
* </ul>
* </pre>
*
* @param element target element
* @return editor that works with a given element if the one is found; <code>null</code> otherwise
*/
@Nullable
public static Editor findEditor(@NotNull PsiElement element) {
PsiFile psiFile = element.getContainingFile();
if (psiFile == null) {
return null;
}
VirtualFile virtualFile = psiFile.getOriginalFile().getVirtualFile();
if (virtualFile == null) {
return null;
}
VirtualFileSystem fileSystem = virtualFile.getFileSystem();
if (fileSystem instanceof LocalFileSystem) {
// Try to find editor for the real file.
final FileEditor[] editors = FileEditorManager.getInstance(psiFile.getProject()).getEditors(virtualFile);
for (FileEditor editor : editors) {
if (editor instanceof TextEditor) {
return ((TextEditor)editor).getEditor();
}
}
}
else if (SwingUtilities.isEventDispatchThread()) {
// We assume that data context from focus-based retrieval should success if performed from EDT.
AsyncResult<DataContext> asyncResult = DataManager.getInstance().getDataContextFromFocus();
if (asyncResult.isDone()) {
Editor editor = CommonDataKeys.EDITOR.getData(asyncResult.getResult());
if (editor != null) {
Document cachedDocument = PsiDocumentManager.getInstance(psiFile.getProject()).getCachedDocument(psiFile);
// Ensure that target editor is found by checking its document against the one from given PSI element.
if (cachedDocument == editor.getDocument()) {
return editor;
}
}
}
}
return null;
}
public static boolean isSymLink(@NotNull final PsiFileSystemItem element) {
final VirtualFile virtualFile = element.getVirtualFile();
return virtualFile != null && virtualFile.is(VFileProperty.SYMLINK);
}
@Nullable
public static VirtualFile asVirtualFile(@Nullable PsiElement element) {
if (element instanceof PsiFileSystemItem) {
PsiFileSystemItem psiFileSystemItem = (PsiFileSystemItem)element;
return psiFileSystemItem.isValid() ? psiFileSystemItem.getVirtualFile() : null;
}
else {
return null;
}
}
}