blob: ab9e668a0057cd11fc86c882cd96a2a7feb9fa22 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.netbeans.modules.java.debug;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.SourcePositions;
import com.sun.source.util.TreePath;
import org.netbeans.api.java.source.support.ErrorAwareTreePathScanner;
import java.awt.BorderLayout;
import java.awt.Color;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyVetoException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.ActionMap;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.ListSelectionModel;
import javax.swing.text.AttributeSet;
import javax.swing.text.Document;
import javax.swing.text.StyleConstants;
import org.netbeans.api.editor.settings.AttributesUtilities;
import org.netbeans.api.java.source.CancellableTask;
import org.netbeans.api.java.source.CompilationInfo;
import org.netbeans.api.java.source.support.CaretAwareJavaSourceTaskFactory;
import org.netbeans.spi.editor.highlighting.support.OffsetsBag;
import org.netbeans.spi.navigator.NavigatorPanel;
import org.openide.cookies.EditorCookie;
import org.openide.explorer.ExplorerManager;
import org.openide.explorer.ExplorerUtils;
import org.openide.explorer.view.BeanTreeView;
import org.openide.filesystems.FileObject;
import org.openide.loaders.DataObject;
import org.openide.loaders.DataObjectNotFoundException;
import org.openide.nodes.Node;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
/**
*
* @author Jan Lahoda
*/
public class TreeNavigatorProviderImpl implements NavigatorPanel {
private JComponent panel;
private final ExplorerManager manager = new ExplorerManager();
/**
* Default constructor for layer instance.
*/
public TreeNavigatorProviderImpl() {
manager.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
if (ExplorerManager.PROP_SELECTED_NODES.equals(evt.getPropertyName())) {
setHighlights(TreeNavigatorJavaSourceFactory.getInstance().getFile(), manager);
}
}
});
}
public String getDisplayName() {
return NbBundle.getMessage(TreeNavigatorProviderImpl.class, "NM_Trees");
}
public String getDisplayHint() {
return NbBundle.getMessage(TreeNavigatorProviderImpl.class, "SD_Trees");
}
public JComponent getComponent() {
if (panel == null) {
final BeanTreeView view = new BeanTreeView();
view.setRootVisible(true);
view.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
class Panel extends JPanel implements ExplorerManager.Provider, Lookup.Provider {
// Make sure action context works correctly:
private final Lookup lookup = ExplorerUtils.createLookup(manager, new ActionMap());
{
setLayout(new BorderLayout());
add(view, BorderLayout.CENTER);
}
public ExplorerManager getExplorerManager() {
return manager;
}
public Lookup getLookup() {
return lookup;
}
}
panel = new Panel();
}
return panel;
}
public Lookup getLookup() {
return null;
}
public void panelActivated(Lookup context) {
TreeNavigatorJavaSourceFactory.getInstance().setLookup(context, new TaskImpl());
TreeNavigatorJavaSourceFactory.CaretAwareFactoryImpl.getInstance().setTask(new SelectingTaskImpl());
}
public void panelDeactivated() {
TreeNavigatorJavaSourceFactory.getInstance().setLookup(Lookup.EMPTY, null);
TreeNavigatorJavaSourceFactory.CaretAwareFactoryImpl.getInstance().setTask(null);
}
static OffsetsBag getBag(Document doc) {
OffsetsBag bag = (OffsetsBag) doc.getProperty(TreeNavigatorProviderImpl.class);
if (bag == null) {
doc.putProperty(TreeNavigatorProviderImpl.class, bag = new OffsetsBag(doc));
}
return bag;
}
static void setHighlights(FileObject file, ExplorerManager manager) {
if (file == null) {
return;
}
try {
DataObject od = DataObject.find(file);
EditorCookie ec = od.getLookup().lookup(EditorCookie.class);
if (ec == null) {
return;
}
Document doc = ec.getDocument();
if (doc == null) {
return;
}
OffsetsBag bag = new OffsetsBag(doc, true);
for (Node n : manager.getSelectedNodes()) {
if (n instanceof OffsetProvider) {
OffsetProvider p = (OffsetProvider) n;
final int start = p.getStart();
final int end = p.getEnd();
final int pref = p.getPreferredPosition();
if (start >= 0 && end >= 0) {
bag.addHighlight(start, end, HIGHLIGHT);
}
if (pref >= 0) {
bag.addHighlight(pref, pref+1, HIGHLIGHT_PREF);
}
}
}
getBag(doc).setHighlights(bag);
} catch (DataObjectNotFoundException ex) {
Logger.getLogger(TreeNavigatorProviderImpl.class.getName()).log(Level.FINE, null, ex);
}
}
private static final AttributeSet HIGHLIGHT = AttributesUtilities.createImmutable(StyleConstants.Background, new Color(150, 190, 180));
private static final AttributeSet HIGHLIGHT_PREF = AttributesUtilities.createImmutable(StyleConstants.Underline, new Color(30, 255, 0));
private final class TaskImpl implements CancellableTask<CompilationInfo> {
private final AtomicBoolean cancel = new AtomicBoolean();
public void cancel() {
cancel.set(true);
}
public void run(CompilationInfo info) {
cancel.set(false);
Node tree = TreeNode.getTree(info, new TreePath(info.getCompilationUnit()), cancel);
if (!cancel.get()) {
manager.setRootContext(tree);
}
}
}
private final class SelectingTaskImpl implements CancellableTask<CompilationInfo> {
public void cancel() {
}
public void run(CompilationInfo info) throws PropertyVetoException {
int pos = CaretAwareJavaSourceTaskFactory.getLastPosition(info.getFileObject());
TreePath tp = plainPathFor(info, pos);
Node toSelect = tp != null ? TreeNode.findNode(manager.getRootContext(), tp) : null;
if (toSelect != null) {
manager.setExploredContext(toSelect);
manager.setSelectedNodes(new Node[] {toSelect});
} else {
manager.setSelectedNodes(new Node[] {});
}
}
}
private static TreePath plainPathFor(final CompilationInfo info, int pos) {
//TODO: TreeUtilities.pathFor handles error trees in a strange way - not sure if intentional, but unusable for the tree navigator
class Result extends Error {
TreePath path;
Result(TreePath path) {
this.path = path;
}
}
class PathFinder extends ErrorAwareTreePathScanner<Void,Void> {
private int pos;
private SourcePositions sourcePositions;
private PathFinder(int pos, SourcePositions sourcePositions) {
this.pos = pos;
this.sourcePositions = sourcePositions;
}
@Override
public Void scan(Tree tree, Void p) {
if (tree != null) {
if (sourcePositions.getStartPosition(getCurrentPath().getCompilationUnit(), tree) < pos && sourcePositions.getEndPosition(getCurrentPath().getCompilationUnit(), tree) >= pos) {
super.scan(tree, p);
throw new Result(new TreePath(getCurrentPath(), tree));
}
}
return null;
}
@Override
public Void visitVariable(VariableTree node, Void p) {
int[] span = info.getTreeUtilities().findNameSpan(node);
if (span != null && span[0] <= pos && pos < span[1]) {
throw new Result(getCurrentPath());
}
return super.visitVariable(node, p);
}
@Override
public Void visitMethod(MethodTree node, Void p) {
int[] span = info.getTreeUtilities().findNameSpan(node);
if (span != null && span[0] <= pos && pos < span[1]) {
throw new Result(getCurrentPath());
}
return super.visitMethod(node, p);
}
}
try {
new PathFinder(pos, info.getTrees().getSourcePositions()).scan(new TreePath(info.getCompilationUnit()), null);
return null;
} catch (Result result) {
return result.path;
}
}
}