blob: 849b01b703b963493de10a6c896143046d777ecc [file] [log] [blame]
/*
* Copyright 2000-2014 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.ide.structureView.impl;
import com.intellij.ide.IdeBundle;
import com.intellij.ide.impl.StructureViewWrapperImpl;
import com.intellij.ide.structureView.*;
import com.intellij.ide.structureView.newStructureView.StructureViewComponent;
import com.intellij.ide.util.treeView.smartTree.TreeElement;
import com.intellij.lang.Language;
import com.intellij.lang.LanguageStructureViewBuilder;
import com.intellij.lang.PsiStructureViewFactory;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.fileTypes.FileTypes;
import com.intellij.openapi.fileTypes.LanguageFileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.templateLanguages.TemplateLanguageFileViewProvider;
import com.intellij.psi.util.PsiModificationTracker;
import com.intellij.util.Alarm;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
/**
* @author peter
*/
public abstract class TemplateLanguageStructureViewBuilder implements StructureViewBuilder {
private final VirtualFile myVirtualFile;
private final Project myProject;
private Language myTemplateDataLanguage;
private StructureViewComposite.StructureViewDescriptor myBaseStructureViewDescriptor;
private FileEditor myFileEditor;
private StructureViewComposite myStructureViewComposite;
private int myBaseLanguageViewDescriptorIndex;
protected TemplateLanguageStructureViewBuilder(PsiElement psiElement) {
myVirtualFile = psiElement.getContainingFile().getVirtualFile();
myProject = psiElement.getProject();
myTemplateDataLanguage = getNotNullViewProvider().getTemplateDataLanguage();
}
private void updateAfterPsiChange() {
if (myProject.isDisposed()) return;
if (myBaseStructureViewDescriptor != null && ((StructureViewComponent)myBaseStructureViewDescriptor.structureView).getTree() == null) return;
ApplicationManager.getApplication().runReadAction(new Runnable(){
@Override
public void run() {
if (!myVirtualFile.isValid()) return;
final TemplateLanguageFileViewProvider provider = getViewProvider();
if (provider == null) return;
StructureViewWrapper structureViewWrapper = StructureViewFactoryEx.getInstanceEx(myProject).getStructureViewWrapper();
if (structureViewWrapper == null) return;
Language baseLanguage = provider.getTemplateDataLanguage();
if (baseLanguage == myTemplateDataLanguage
&& (myBaseStructureViewDescriptor == null || isPsiValid(myBaseStructureViewDescriptor))) {
updateBaseLanguageView();
}
else {
myTemplateDataLanguage = baseLanguage;
((StructureViewWrapperImpl)structureViewWrapper).rebuild();
}
}
});
}
private static boolean isPsiValid(@NotNull StructureViewComposite.StructureViewDescriptor baseStructureViewDescriptor) {
final StructureViewComponent view = (StructureViewComponent)baseStructureViewDescriptor.structureView;
if (view.isDisposed()) return false;
final Object root = view.getTreeStructure().getRootElement();
if (root instanceof StructureViewComponent.StructureViewTreeElementWrapper) {
final TreeElement value = ((StructureViewComponent.StructureViewTreeElementWrapper)root).getValue();
if (value instanceof StructureViewTreeElement) {
final Object psi = ((StructureViewTreeElement)value).getValue();
if (psi instanceof PsiElement) {
return ((PsiElement)psi).isValid();
}
}
}
return true;
}
@Nullable
private TemplateLanguageFileViewProvider getViewProvider() {
final FileViewProvider provider = PsiManager.getInstance(myProject).findViewProvider(myVirtualFile);
return provider instanceof TemplateLanguageFileViewProvider ? (TemplateLanguageFileViewProvider)provider : null;
}
@NotNull
private TemplateLanguageFileViewProvider getNotNullViewProvider() {
final FileViewProvider provider = PsiManager.getInstance(myProject).findViewProvider(myVirtualFile);
if (provider == null || !(provider instanceof TemplateLanguageFileViewProvider)) {
throw new AssertionError("Not a template view provider for " + myVirtualFile + ": " + provider);
}
return (TemplateLanguageFileViewProvider)provider;
}
private void updateBaseLanguageView() {
if (myBaseStructureViewDescriptor == null || !myProject.isOpen()) return;
final StructureViewComponent view = (StructureViewComponent)myBaseStructureViewDescriptor.structureView;
if (view.isDisposed()) return;
StructureViewState state = view.getState();
List<PsiAnchor> expanded = collectAnchors(state.getExpandedElements());
List<PsiAnchor> selected = collectAnchors(state.getSelectedElements());
updateTemplateDataFileView();
if (view.isDisposed()) return;
for (PsiAnchor pointer : expanded) {
PsiElement element = pointer.retrieve();
if (element != null) {
view.expandPathToElement(element);
}
}
for (PsiAnchor pointer : selected) {
PsiElement element = pointer.retrieve();
if (element != null) {
view.addSelectionPathTo(element);
}
}
}
private static List<PsiAnchor> collectAnchors(final Object[] expandedElements) {
List<PsiAnchor> expanded = new ArrayList<PsiAnchor>(expandedElements == null ? 0 : expandedElements.length);
if (expandedElements != null) {
for (Object element : expandedElements) {
if (element instanceof PsiElement && ((PsiElement) element).isValid()) {
expanded.add(PsiAnchor.create((PsiElement)element));
}
}
}
return expanded;
}
@Override
@NotNull
public StructureView createStructureView(FileEditor fileEditor, @NotNull Project project) {
myFileEditor = fileEditor;
List<StructureViewComposite.StructureViewDescriptor> viewDescriptors = new ArrayList<StructureViewComposite.StructureViewDescriptor>();
final TemplateLanguageFileViewProvider provider = getNotNullViewProvider();
final StructureViewComposite.StructureViewDescriptor structureViewDescriptor = createMainView(fileEditor, provider.getPsi(provider.getBaseLanguage()));
if (structureViewDescriptor != null) viewDescriptors.add(structureViewDescriptor);
myBaseLanguageViewDescriptorIndex = -1;
final Language dataLanguage = provider.getTemplateDataLanguage();
updateTemplateDataFileView();
if (myBaseStructureViewDescriptor != null) {
viewDescriptors.add(myBaseStructureViewDescriptor);
myBaseLanguageViewDescriptorIndex = viewDescriptors.size() - 1;
}
for (final Language language : provider.getLanguages()) {
if (language != dataLanguage && language != provider.getBaseLanguage()) {
ContainerUtil.addIfNotNull(createBaseLanguageStructureView(fileEditor, language), viewDescriptors);
}
}
StructureViewComposite.StructureViewDescriptor[] array = viewDescriptors.toArray(new StructureViewComposite.StructureViewDescriptor[viewDescriptors.size()]);
myStructureViewComposite = new StructureViewComposite(array);
project.getMessageBus().connect(myStructureViewComposite).subscribe(PsiModificationTracker.TOPIC, new PsiModificationTracker.Listener() {
final Alarm alarm = new Alarm(myStructureViewComposite);
@Override
public void modificationCountChanged() {
alarm.cancelAllRequests();
alarm.addRequest(new Runnable() {
@Override
public void run() {
updateAfterPsiChange();
}
}, 300, ModalityState.NON_MODAL);
}
});
return myStructureViewComposite;
}
protected abstract StructureViewComposite.StructureViewDescriptor createMainView(FileEditor fileEditor, PsiFile mainFile);
@Nullable
private StructureViewComposite.StructureViewDescriptor createBaseLanguageStructureView(final FileEditor fileEditor, final Language language) {
if (!myVirtualFile.isValid()) return null;
final TemplateLanguageFileViewProvider viewProvider = getViewProvider();
if (viewProvider == null) return null;
final PsiFile dataFile = viewProvider.getPsi(language);
if (dataFile == null || !isAcceptableBaseLanguageFile(dataFile)) return null;
final PsiStructureViewFactory factory = LanguageStructureViewBuilder.INSTANCE.forLanguage(language);
if (factory == null) return null;
final StructureViewBuilder builder = factory.getStructureViewBuilder(dataFile);
if (builder == null) return null;
StructureView structureView = builder.createStructureView(fileEditor, myProject);
return new StructureViewComposite.StructureViewDescriptor(IdeBundle.message("tab.structureview.baselanguage.view", language.getDisplayName()), structureView, findFileType(language).getIcon());
}
protected boolean isAcceptableBaseLanguageFile(PsiFile dataFile) {
return true;
}
private void updateTemplateDataFileView() {
final TemplateLanguageFileViewProvider provider = getViewProvider();
final Language newDataLanguage = provider == null ? null : provider.getTemplateDataLanguage();
if (myBaseStructureViewDescriptor != null) {
if (myTemplateDataLanguage == newDataLanguage) return;
Disposer.dispose(myBaseStructureViewDescriptor.structureView);
}
if (newDataLanguage != null) {
myBaseStructureViewDescriptor = createBaseLanguageStructureView(myFileEditor, newDataLanguage);
if (myStructureViewComposite != null) {
myStructureViewComposite.setStructureView(myBaseLanguageViewDescriptorIndex, myBaseStructureViewDescriptor);
}
}
}
@NotNull
private static FileType findFileType(final Language language) {
FileType[] registeredFileTypes = FileTypeManager.getInstance().getRegisteredFileTypes();
for (FileType fileType : registeredFileTypes) {
if (fileType instanceof LanguageFileType && ((LanguageFileType)fileType).getLanguage() == language) {
return fileType;
}
}
return FileTypes.UNKNOWN;
}
}