blob: a8c254c04e2569301f147b0a306b24d437be1569 [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;
import com.intellij.ide.util.treeView.smartTree.*;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.event.CaretAdapter;
import com.intellij.openapi.editor.event.CaretEvent;
import com.intellij.openapi.util.Disposer;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiEditorUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ReflectionUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* The standard {@link StructureViewModel} implementation which is linked to a text editor.
*
* @see com.intellij.ide.structureView.TreeBasedStructureViewBuilder#createStructureViewModel(Editor editor)
*/
public abstract class TextEditorBasedStructureViewModel implements StructureViewModel, ProvidingTreeModel {
private final Editor myEditor;
private final PsiFile myPsiFile;
private final Disposable myDisposable = Disposer.newDisposable();
private final List<FileEditorPositionListener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList();
private List<ModelListener> myModelListeners = new ArrayList<ModelListener>(2);
/**
* Creates a structure view model instance linked to a text editor displaying the specified
* file.
*
* @param psiFile the file for which the structure view model is requested.
*/
protected TextEditorBasedStructureViewModel(@NotNull PsiFile psiFile) {
this(PsiEditorUtil.Service.getInstance().findEditorByPsiElement(psiFile), psiFile);
}
/**
* Creates a structure view model instance linked to the specified text editor.
*
* @param editor the editor for which the structure view model is requested.
*/
protected TextEditorBasedStructureViewModel(final Editor editor) {
this(editor, null);
}
protected TextEditorBasedStructureViewModel(Editor editor, PsiFile file) {
myEditor = editor;
myPsiFile = file;
if (editor != null) {
EditorFactory.getInstance().getEventMulticaster().addCaretListener(new CaretAdapter() {
@Override
public void caretPositionChanged(CaretEvent e) {
if (e.getEditor().equals(myEditor)) {
for (FileEditorPositionListener listener : myListeners) {
listener.onCurrentElementChanged();
}
}
}
}, myDisposable);
}
}
@Override
public final void addEditorPositionListener(@NotNull FileEditorPositionListener listener) {
myListeners.add(listener);
}
@Override
public final void removeEditorPositionListener(@NotNull FileEditorPositionListener listener) {
myListeners.remove(listener);
}
@Override
public void dispose() {
Disposer.dispose(myDisposable);
myModelListeners.clear();
}
public void fireModelUpdate() {
for (ModelListener listener : myModelListeners) {
listener.onModelChanged();
}
}
@Override
public boolean shouldEnterElement(final Object element) {
return false;
}
@Override
public Object getCurrentEditorElement() {
if (myEditor == null) return null;
final int offset = myEditor.getCaretModel().getOffset();
final PsiFile file = getPsiFile();
return findAcceptableElement(file.getViewProvider().findElementAt(offset, file.getLanguage()));
}
@Nullable
protected Object findAcceptableElement(PsiElement element) {
while (element != null && !(element instanceof PsiFile)) {
if (isSuitable(element)) return element;
element = element.getParent();
}
return null;
}
protected PsiFile getPsiFile() {
return myPsiFile;
}
protected boolean isSuitable(final PsiElement element) {
if (element == null) return false;
final Class[] suitableClasses = getSuitableClasses();
for (Class suitableClass : suitableClasses) {
if (ReflectionUtil.isAssignable(suitableClass, element.getClass())) return true;
}
return false;
}
@Override
public void addModelListener(@NotNull ModelListener modelListener) {
myModelListeners.add(modelListener);
}
@Override
public void removeModelListener(@NotNull ModelListener modelListener) {
myModelListeners.remove(modelListener);
}
/**
* Returns the list of PSI element classes which are shown as structure view elements.
* When determining the current editor element, the PSI tree is walked up until an element
* matching one of these classes is found.
*
* @return the list of classes
*/
@NotNull
protected Class[] getSuitableClasses() {
return ArrayUtil.EMPTY_CLASS_ARRAY;
}
protected Editor getEditor() {
return myEditor;
}
@Override
@NotNull
public Grouper[] getGroupers() {
return Grouper.EMPTY_ARRAY;
}
@Override
@NotNull
public Sorter[] getSorters() {
return Sorter.EMPTY_ARRAY;
}
@Override
@NotNull
public Filter[] getFilters() {
return Filter.EMPTY_ARRAY;
}
@NotNull
@Override
public Collection<NodeProvider> getNodeProviders() {
return Collections.emptyList();
}
@Override
public boolean isEnabled(@NotNull NodeProvider provider) {
return false;
}
}