blob: 30bf093738fa9ca9e7cdb0bdef91c5469fcd583e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2015 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.ant.internal.ui.editor.text;
import java.util.Iterator;
import java.util.StringTokenizer;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.types.AbstractFileSet;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.PatternSet;
import org.eclipse.ant.internal.core.IAntCoreConstants;
import org.eclipse.ant.internal.launching.debug.model.AntProperty;
import org.eclipse.ant.internal.launching.debug.model.AntStackFrame;
import org.eclipse.ant.internal.launching.debug.model.AntValue;
import org.eclipse.ant.internal.ui.editor.AntEditor;
import org.eclipse.ant.internal.ui.editor.AntEditorSourceViewerConfiguration;
import org.eclipse.ant.internal.ui.model.AntElementNode;
import org.eclipse.ant.internal.ui.model.AntModel;
import org.eclipse.ant.internal.ui.model.AntPropertyNode;
import org.eclipse.ant.internal.ui.model.IAntModel;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.jface.internal.text.html.HTMLPrinter;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DefaultInformationControl;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IInformationControl;
import org.eclipse.jface.text.IInformationControlCreator;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextHover;
import org.eclipse.jface.text.ITextHoverExtension;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.information.IInformationProviderExtension2;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.editors.text.EditorsUI;
public class XMLTextHover implements ITextHover, ITextHoverExtension, IInformationProviderExtension2 {
private AntEditor fEditor;
public XMLTextHover(AntEditor editor) {
super();
fEditor = editor;
}
/*
* Formats a message as HTML text. Expects the message to already be properly escaped
*/
private String formatMessage(String message) {
StringBuilder buffer = new StringBuilder();
HTMLPrinter.addPageProlog(buffer);
HTMLPrinter.addParagraph(buffer, message);
HTMLPrinter.addPageEpilog(buffer);
return buffer.toString();
}
/*
* Formats a message as HTML text.
*/
private String formatPathMessage(String[] list) {
StringBuilder buffer = new StringBuilder();
HTMLPrinter.addPageProlog(buffer);
HTMLPrinter.addSmallHeader(buffer, AntEditorTextMessages.XMLTextHover_4);
HTMLPrinter.startBulletList(buffer);
for (int i = 0; i < list.length; i++) {
HTMLPrinter.addBullet(buffer, list[i]);
}
HTMLPrinter.endBulletList(buffer);
HTMLPrinter.addPageEpilog(buffer);
return buffer.toString();
}
@Override
public String getHoverInfo(ITextViewer textViewer, IRegion hoverRegion) {
if (!(textViewer instanceof ISourceViewer)) {
return null;
}
ISourceViewer sourceViewer = (ISourceViewer) textViewer;
IAnnotationModel model = sourceViewer.getAnnotationModel();
if (model != null) {
String message = getAnnotationModelHoverMessage(model, hoverRegion);
if (message != null) {
return message;
}
}
AntModel antModel = fEditor.getAntModel();
if (antModel == null) { // the ant model has not been created yet
return null;
}
return getAntModelHoverMessage(antModel, hoverRegion, textViewer);
}
private String getAntModelHoverMessage(AntModel antModel, IRegion hoverRegion, ITextViewer textViewer) {
try {
IDocument document = textViewer.getDocument();
int offset = hoverRegion.getOffset();
int length = hoverRegion.getLength();
String text = document.get(offset, length);
String value;
AntElementNode node = antModel.getNode(offset, false);
if (document.get(offset - 2, 2).equals("${") || node instanceof AntPropertyNode) { //$NON-NLS-1$
AntStackFrame frame = getFrame();
if (frame != null) {// active Ant debug session
AntProperty property = frame.findProperty(text);
if (property != null) {
return ((AntValue) property.getValue()).getValueString();
}
}
value = antModel.getPropertyValue(text);
if (value != null) {
return formatMessage(HTMLPrinter.convertToHTMLContent(value));
}
}
value = antModel.getTargetDescription(text);
if (value != null) {
return formatMessage(HTMLPrinter.convertToHTMLContent(value));
}
Object referencedObject = antModel.getReferenceObject(text);
if (referencedObject != null) {
if (referencedObject instanceof Path) {
return formatPathMessage(((Path) referencedObject).list());
} else if (referencedObject instanceof PatternSet) {
return formatPatternSetMessage((PatternSet) referencedObject);
} else if (referencedObject instanceof AbstractFileSet) {
return formatFileSetMessage((AbstractFileSet) referencedObject);
}
}
}
catch (BadLocationException e) {
// do nothing
}
catch (BuildException be) {
return be.getMessage();
}
return null;
}
private String getAnnotationModelHoverMessage(IAnnotationModel model, IRegion hoverRegion) {
Iterator<Annotation> e = model.getAnnotationIterator();
while (e.hasNext()) {
Annotation a = e.next();
if (a instanceof XMLProblemAnnotation) {
Position p = model.getPosition(a);
if (p.overlapsWith(hoverRegion.getOffset(), hoverRegion.getLength())) {
String msg = a.getText();
if (msg != null && msg.trim().length() > 0) {
return formatMessage(msg);
}
}
}
}
return null;
}
private String formatFileSetMessage(AbstractFileSet set) {
FileScanner fileScanner = new FileScanner();
IAntModel antModel = fEditor.getAntModel();
Project project = antModel.getProjectNode().getProject();
set.setupDirectoryScanner(fileScanner, project);
String[] excludedPatterns = fileScanner.getExcludesPatterns();
String[] includesPatterns = fileScanner.getIncludePatterns();
return formatSetMessage(includesPatterns, excludedPatterns);
}
private String formatPatternSetMessage(PatternSet set) {
IAntModel antModel = fEditor.getAntModel();
Project project = antModel.getProjectNode().getProject();
String[] includes = set.getIncludePatterns(project);
String[] excludes = set.getExcludePatterns(project);
return formatSetMessage(includes, excludes);
}
private String formatSetMessage(String[] includes, String[] excludes) {
StringBuilder buffer = new StringBuilder();
HTMLPrinter.addPageProlog(buffer);
if (includes != null && includes.length > 0) {
HTMLPrinter.addSmallHeader(buffer, AntEditorTextMessages.XMLTextHover_5);
for (int i = 0; i < includes.length; i++) {
HTMLPrinter.addBullet(buffer, includes[i]);
}
}
HTMLPrinter.addParagraph(buffer, IAntCoreConstants.EMPTY_STRING);
HTMLPrinter.addParagraph(buffer, IAntCoreConstants.EMPTY_STRING);
if (excludes != null && excludes.length > 0) {
HTMLPrinter.addSmallHeader(buffer, AntEditorTextMessages.XMLTextHover_6);
for (int i = 0; i < excludes.length; i++) {
HTMLPrinter.addBullet(buffer, excludes[i]);
}
}
HTMLPrinter.addPageEpilog(buffer);
return buffer.toString();
}
@Override
public IRegion getHoverRegion(ITextViewer textViewer, int offset) {
if (textViewer != null) {
return getRegion(textViewer, offset);
}
return null;
}
public static IRegion getRegion(ITextViewer textViewer, int offset) {
IDocument document = textViewer.getDocument();
int start = -1;
int end = -1;
IRegion region = null;
try {
int pos = offset;
char c;
if (document.getChar(pos) == '"') {
pos--;
}
while (pos >= 0) {
c = document.getChar(pos);
if (c != '.' && c != '-' && c != '/' && c != '\\' && c != ' ' && c != ')' && c != '(' && c != ':'
&& !Character.isJavaIdentifierPart(c) && pos != offset)
break;
--pos;
}
start = pos;
pos = offset;
int length = document.getLength();
while (pos < length) {
c = document.getChar(pos);
if (c != '.' && c != '-' && c != '/' && c != '\\' && c != ' ' && c != ')' && c != '(' && c != ':'
&& !Character.isJavaIdentifierPart(c))
break;
if (c == '/' && (document.getLength() - 1) > (pos + 1) && document.getChar(pos + 1) == '>') {
// e.g. <name/>
break;
}
++pos;
}
end = pos;
}
catch (BadLocationException x) {
// do nothing
}
if (start > -1 && end > -1) {
if (start == offset && end == offset) {
return new Region(offset, 0);
} else if (start == offset) {
return new Region(start, end - start);
} else {
try { // correct for spaces at beginning or end
while (document.getChar(start + 1) == ' ') {
start++;
}
while (document.getChar(end - 1) == ' ') {
end--;
}
}
catch (BadLocationException e) {
// do nothing
}
region = new Region(start + 1, end - start - 1);
}
}
if (region != null) {
try {
char c = document.getChar(region.getOffset() - 1);
if (c == '"') {
if (document.get(offset, region.getLength()).indexOf(',') != -1) {
region = cleanRegionForNonProperty(offset, document, region);
}
} else if (c != '{') {
region = cleanRegionForNonProperty(offset, document, region);
}
}
catch (BadLocationException e) {
// do nothing
}
}
return region;
}
private static IRegion cleanRegionForNonProperty(int offset, IDocument document, IRegion region) throws BadLocationException {
// do not allow spaces in region that is not a property
IRegion r = region;
String text = document.get(r.getOffset(), r.getLength());
if (text.startsWith("/")) { //$NON-NLS-1$
text = text.substring(1);
r = new Region(r.getOffset() + 1, r.getLength() - 1);
}
StringTokenizer tokenizer = new StringTokenizer(text, " "); //$NON-NLS-1$
if (tokenizer.countTokens() != 1) {
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
int index = text.indexOf(token);
if (r.getOffset() + index <= offset && r.getOffset() + index + token.length() >= offset) {
r = new Region(r.getOffset() + index, token.length());
break;
}
}
}
return r;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.text.ITextHoverExtension#getHoverControlCreator()
*/
@Override
public IInformationControlCreator getHoverControlCreator() {
return new IInformationControlCreator() {
@Override
public IInformationControl createInformationControl(Shell parent) {
return new DefaultInformationControl(parent, EditorsUI.getTooltipAffordanceString());
}
};
}
/**
* Returns the stack frame in which to search for properties, or <code>null</code> if none.
*
* @return the stack frame in which to search for properties, or <code>null</code> if none
*/
private AntStackFrame getFrame() {
IAdaptable adaptable = DebugUITools.getDebugContext();
if (adaptable != null) {
return adaptable.getAdapter(AntStackFrame.class);
}
return null;
}
/*
* @see org.eclipse.jface.text.information.IInformationProviderExtension2#getInformationPresenterControlCreator()
*
* @since 3.3
*/
@Override
public IInformationControlCreator getInformationPresenterControlCreator() {
return AntEditorSourceViewerConfiguration.getInformationPresenterControlCreator();
}
}