/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved.
*
* Oracle and Java are registered trademarks of Oracle and/or its affiliates.
* Other names may be trademarks of their respective owners.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common
* Development and Distribution License("CDDL") (collectively, the
* "License"). You may not use this file except in compliance with the
* License. You can obtain a copy of the License at
* http://www.netbeans.org/cddl-gplv2.html
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
* specific language governing permissions and limitations under the
* License. When distributing the software, include this License Header
* Notice in each file and include the License file at
* nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the
* License Header, with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* Contributor(s):
*
* The Original Software is NetBeans. The Initial Developer of the Original
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2008 Sun
* Microsystems, Inc. All Rights Reserved.
*
* If you wish your version of this file to be governed by only the CDDL
* or only the GPL Version 2, indicate your decision by adding
* "[Contributor] elects to include this software in this distribution
* under the [CDDL or GPL Version 2] license." If you do not indicate a
* single choice of license, a recipient has the option to distribute
* your version of this file under either the CDDL, the GPL Version 2 or
* to extend the choice of license to its licensees as provided above.
* However, if you add GPL Version 2 code and therefore, elected the GPL
* Version 2 license, then the option applies only if the new code is
* made subject to such option by the copyright holder.
*/
package org.netbeans.modules.ruby.debugger;
import java.io.IOException;
import javax.swing.JEditorPane;
import javax.swing.text.BadLocationException;
import javax.swing.text.Element;
import javax.swing.text.StyledDocument;
import org.jrubyparser.ast.INameNode;
import org.jrubyparser.ast.InstVarNode;
import org.jrubyparser.ast.Node;
import org.jrubyparser.ast.NodeType;
import org.netbeans.modules.ruby.AstUtilities;
import org.netbeans.spi.debugger.ui.EditorContextDispatcher;
import org.openide.cookies.EditorCookie;
import org.openide.filesystems.FileObject;
import org.openide.loaders.DataObject;
import org.openide.text.Annotation;
import org.openide.text.DataEditorSupport;
import org.openide.text.Line;
import org.openide.text.Line.Part;
import org.openide.text.NbDocument;
import org.openide.util.RequestProcessor;
import org.rubyforge.debugcommons.model.RubyValue;
import org.rubyforge.debugcommons.model.RubyVariable;
public final class ToolTipAnnotation extends Annotation implements Runnable {
private static final Boolean SKIP_BALLOON_EVAL = Boolean.getBoolean("ruby.debugger.skip.balloon.evaluation"); // NOI18N
private static final RequestProcessor requestProcessor = new RequestProcessor("Ruby tooltip annotations");
private Part lp;
private EditorCookie ec;
public String getShortDescription() {
RubySession session = Util.getCurrentSession();
if (session == null) { return null; }
Part _lp = (Part) getAttachedAnnotatable();
if (_lp == null) { return null; }
Line line = _lp.getLine();
DataObject dob = DataEditorSupport.findDataObject(line);
if (dob == null) { return null; }
EditorCookie _ec = dob.getCookie(EditorCookie.class);
if (_ec == null) { return null; }
this.lp = _lp;
this.ec = _ec;
requestProcessor.post(this);
return null;
}
public void run() {
if (SKIP_BALLOON_EVAL) {
return;
}
if (lp == null || ec == null) { return; }
StyledDocument doc;
try {
doc = ec.openDocument();
} catch (IOException ex) {
return;
}
JEditorPane ep = EditorContextDispatcher.getDefault().getCurrentEditor();
if (ep == null) { return; }
String expression = getIdentifier(doc, ep, NbDocument.findLineOffset(doc, lp.getLine().getLineNumber()) + lp.getColumn());
if (expression == null) { return; }
RubySession session = Util.getCurrentSession();
if (session == null) { return; }
RubyVariable var = session.inspectExpression(expression);
if (var == null) { return; }
RubyValue value = var.getValue();
if (value == null) { return; }
String stringVal = value.getValueString();
if (stringVal == null || stringVal.equals(expression)) { return; }
String toolTipText = expression + " = " + stringVal; // NOI18N
firePropertyChange(PROP_SHORT_DESCRIPTION, null, toolTipText);
}
public String getAnnotationType() {
return null; // Currently return null annotation type
}
/** TODO: based on the Java. Tune it up appropriately for Ruby. */
private static String getIdentifier(final StyledDocument doc, final JEditorPane ep, final int offset) {
if ((ep.getSelectionStart() <= offset) && (offset <= ep.getSelectionEnd())) {
return ep.getSelectedText();
}
int line = NbDocument.findLineNumber(doc, offset);
int col = NbDocument.findLineColumn(doc, offset);
Element lineElem = NbDocument.findLineRootElement(doc).getElement(line);
if (lineElem == null) {
return null;
}
int lineStartOffset = lineElem.getStartOffset();
int lineLen = lineElem.getEndOffset() - lineStartOffset;
if (col + 1 >= lineLen) {
// do not evaluate when mouse hover behind the end of line (112662)
return null;
}
FileObject fo = EditorContextDispatcher.getDefault().getCurrentFile();
if (fo == null) {
// can this happen??
return null;
}
return getExpressionToEvaluate(fo, offset);
}
static String getExpressionToEvaluate(FileObject fo, int offset) {
Node root = AstUtilities.getRoot(fo);
if (root == null) {
return null;
}
Node node = AstUtilities.findNodeAtOffset(root, offset);
if (node == null) {
return null;
}
// handles the case when the caret is placed just before the
// expression to evaluate, e.g. "^var.foo"
if (node.getNodeType() == NodeType.NEWLINENODE) {
node = AstUtilities.findNodeAtOffset(root, offset + 1);
}
if (shouldEvaluate(node) && node instanceof INameNode) {
return AstUtilities.getName(node);
}
return null;
}
private static boolean shouldEvaluate(Node node) {
if (node.getNodeType() == null) {
return false;
}
// the types we want to evaluate without forcing the user
// to select anything. these should be safe to evaluate without
// side effects (unlike evaluating e.g. method calls).
switch (node.getNodeType()) {
case ARGUMENTNODE:
case DVARNODE:
case DASGNNODE:
case SELFNODE:
case LOCALVARNODE:
case LOCALASGNNODE:
case INSTVARNODE:
case INSTASGNNODE:
case GLOBALVARNODE:
case GLOBALASGNNODE:
case CONSTNODE:
case CONSTDECLNODE:
case CLASSVARNODE:
case CLASSVARASGNNODE:
case NILNODE:
case TRUENODE:
case FALSENODE:
return true;
default:
return false;
}
}
}