| /******************************************************************************* |
| * 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 (String element : list) { |
| HTMLPrinter.addBullet(buffer, element); |
| } |
| 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 (String include : includes) { |
| HTMLPrinter.addBullet(buffer, include); |
| } |
| } |
| 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 (String exclude : excludes) { |
| HTMLPrinter.addBullet(buffer, exclude); |
| } |
| } |
| 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(); |
| } |
| } |