blob: a1891e7a52a4f268d9b15a74b71c9e35e60b58cb [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2016 Red Hat, Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat Incorporated - initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.internal.autotools.ui.text.hover;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.eclipse.cdt.autotools.core.AutotoolsPlugin;
import org.eclipse.cdt.autotools.ui.AutotoolsUIPlugin;
import org.eclipse.cdt.autotools.ui.editors.AutoconfEditor;
import org.eclipse.cdt.autotools.ui.editors.AutoconfMacro;
import org.eclipse.cdt.autotools.ui.editors.IAutotoolEditorActionDefinitionIds;
import org.eclipse.cdt.internal.autotools.core.AutotoolsPropertyConstants;
import org.eclipse.cdt.internal.autotools.ui.CWordFinder;
import org.eclipse.cdt.internal.autotools.ui.HTMLPrinter;
import org.eclipse.cdt.internal.autotools.ui.HTMLTextPresenter;
import org.eclipse.cdt.internal.autotools.ui.preferences.AutotoolsEditorPreferenceConstants;
import org.eclipse.core.filesystem.URIUtil;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DefaultInformationControl;
import org.eclipse.jface.text.IDocument;
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.Region;
import org.eclipse.swt.graphics.Point;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.keys.IBindingService;
import org.osgi.framework.Bundle;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
public class AutoconfTextHover implements ITextHover, ITextHoverExtension {
public static final String LOCAL_AUTOCONF_MACROS_DOC_NAME = "macros/acmacros";
public static final String LOCAL_AUTOMAKE_MACROS_DOC_NAME = "macros/ammacros";
public static final String AUTOCONF_MACROS_DOC_NAME = "http://www.sourceware.org/eclipse/autotools/acmacros"; //$NON-NLS-1$
public static final String AUTOMAKE_MACROS_DOC_NAME = "http://www.sourceware.org/eclipse/autotools/ammacros"; //$NON-NLS-1$
private static class AutotoolsHoverDoc {
public Document[] documents = new Document[2];
public AutotoolsHoverDoc(Document acDocument, Document amDocument) {
this.documents[0] = acDocument;
this.documents[1] = amDocument;
}
public Document getAcDocument() {
return documents[0];
}
public Document getAmDocument() {
return documents[1];
}
public Document[] getDocuments() {
return documents;
}
};
private static Map<String, Document> acHoverDocs;
private static Map<String, Document> amHoverDocs;
private static Map<Document, ArrayList<AutoconfMacro>> acHoverMacros;
private static String fgStyleSheet;
private static AutoconfEditor fEditor;
/* Mapping key to action */
private static IBindingService fBindingService = PlatformUI.getWorkbench().getAdapter(IBindingService.class);
public static String getAutoconfMacrosDocName(String version) {
return AUTOCONF_MACROS_DOC_NAME + "-" //$NON-NLS-1$
+ version
+ ".xml"; //$NON-NLS-1$
}
public static String getLocalAutoconfMacrosDocName(String version) {
return LOCAL_AUTOCONF_MACROS_DOC_NAME + "-" //$NON-NLS-1$
+ version
+ ".xml"; //$NON-NLS-1$
}
/* Get the preferences default for the autoconf macros document name. */
public static String getDefaultAutoconfMacrosVer() {
return AutotoolsPlugin.getDefault().getPreferenceStore().getString(AutotoolsEditorPreferenceConstants.AUTOCONF_VERSION);
}
public static String getAutomakeMacrosDocName(String version) {
return AUTOMAKE_MACROS_DOC_NAME + "-" //$NON-NLS-1$
+ version
+ ".xml"; //$NON-NLS-1$
}
public static String getLocalAutomakeMacrosDocName(String version) {
return LOCAL_AUTOMAKE_MACROS_DOC_NAME + "-" //$NON-NLS-1$
+ version
+ ".xml"; //$NON-NLS-1$
}
/* Get the preferences default for the autoconf macros document name. */
public static String getDefaultAutomakeMacrosVer() {
return AutotoolsPlugin.getDefault().getPreferenceStore().getString(AutotoolsEditorPreferenceConstants.AUTOMAKE_VERSION);
}
protected static Document getACDoc(String acDocVer) {
Document acDocument = null;
if (acHoverDocs == null) {
acHoverDocs = new HashMap<>();
}
acDocument = acHoverDocs.get(acDocVer);
if (acDocument == null) {
Document doc = null;
try {
// see comment in initialize()
try {
InputStream docStream = null;
try {
URI uri = new URI(getLocalAutoconfMacrosDocName(acDocVer));
IPath p = URIUtil.toPath(uri);
// Try to open the file as local to this plug-in.
docStream = FileLocator.openStream(AutotoolsUIPlugin.getDefault().getBundle(), p, false);
} catch (IOException e) {
// Local open failed. Try normal external location.
URI acDoc = new URI(getAutoconfMacrosDocName(acDocVer));
IPath p = URIUtil.toPath(acDoc);
if (p == null) {
URL url = acDoc.toURL();
docStream = url.openStream();
} else {
docStream = new FileInputStream(p.toFile());
}
}
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(false);
try {
DocumentBuilder builder = factory.newDocumentBuilder();
doc = builder.parse(docStream);
} catch (SAXException | ParserConfigurationException | IOException saxEx) {
doc = null;
} finally {
if (docStream != null)
docStream.close();
}
} catch (FileNotFoundException|MalformedURLException|URISyntaxException e) {
AutotoolsPlugin.log(e);
}
acDocument = doc;
}
catch (IOException ioe) {
}
}
acHoverDocs.put(acDocVer, acDocument);
return acDocument;
}
protected static Document getAMDoc(String amDocVer) {
Document amDocument = null;
if (amHoverDocs == null) {
amHoverDocs = new HashMap<>();
}
amDocument = amHoverDocs.get(amDocVer);
if (amDocument == null) {
Document doc = null;
try {
// see comment in initialize()
try {
InputStream docStream = null;
try {
URI uri = new URI(getLocalAutomakeMacrosDocName(amDocVer));
IPath p = URIUtil.toPath(uri);
// Try to open the file as local to this plug-in.
docStream = FileLocator.openStream(AutotoolsUIPlugin.getDefault().getBundle(), p, false);
} catch (IOException e) {
// Local open failed. Try normal external location.
URI acDoc = new URI(getAutomakeMacrosDocName(amDocVer));
IPath p = URIUtil.toPath(acDoc);
if (p == null) {
URL url = acDoc.toURL();
docStream = url.openStream();
} else {
docStream = new FileInputStream(p.toFile());
}
}
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(false);
try {
DocumentBuilder builder = factory.newDocumentBuilder();
doc = builder.parse(docStream);
} catch (SAXException | ParserConfigurationException | IOException ex) {
doc = null;
} finally {
if (docStream != null)
docStream.close();
}
} catch (FileNotFoundException|MalformedURLException|URISyntaxException e) {
AutotoolsPlugin.log(e);
}
amDocument = doc;
}
catch (IOException ioe) {
}
}
amHoverDocs.put(amDocVer, amDocument);
return amDocument;
}
protected static AutotoolsHoverDoc getHoverDoc(IEditorInput input) {
String acDocVer = getDefaultAutoconfMacrosVer();
String amDocVer = getDefaultAutomakeMacrosVer();
if (input instanceof IFileEditorInput) {
IFileEditorInput fe = (IFileEditorInput)input;
IFile f = fe.getFile();
IProject p = f.getProject();
try {
String acVer = p.getPersistentProperty(AutotoolsPropertyConstants.AUTOCONF_VERSION);
if (acVer != null)
acDocVer = acVer;
else { // look for compat project properties
acVer = p.getPersistentProperty(AutotoolsPropertyConstants.AUTOCONF_VERSION_COMPAT);
if (acVer != null)
acDocVer = acVer;
}
} catch (CoreException ce1) {
// do nothing
}
try {
String amVer = p.getPersistentProperty(AutotoolsPropertyConstants.AUTOMAKE_VERSION);
if (amVer != null)
amDocVer = amVer;
else { // look for compat project properties
amVer = p.getPersistentProperty(AutotoolsPropertyConstants.AUTOMAKE_VERSION_COMPAT);
if (amVer != null)
amDocVer = amVer;
}
} catch (CoreException ce2) {
// do nothing
}
}
Document ac_document = getACDoc(acDocVer);
Document am_document = getAMDoc(amDocVer);
return new AutoconfTextHover.AutotoolsHoverDoc(ac_document, am_document);
}
public AutoconfTextHover(AutoconfEditor editor) {
fEditor = editor;
}
public static String getIndexedInfo(String name, AutoconfEditor editor) {
AutotoolsHoverDoc h = getHoverDoc(editor.getEditorInput());
String x = getIndexedInfoFromDocument(name, h.getAcDocument());
if (x == null)
x = getIndexedInfoFromDocument(name, h.getAmDocument());
return x;
}
private static String getIndexedInfoFromDocument(String name, Document document) {
StringBuilder buffer = new StringBuilder();
if (document != null && name != null) {
Element elem = document.getElementById(name);
if (null != elem) {
int prototypeCount = 0;
buffer.append("<B>Macro:</B> ").append(name);
NodeList nl = elem.getChildNodes();
for (int i = 0; i < nl.getLength(); ++i) {
Node n = nl.item(i);
String nodeName = n.getNodeName();
if (nodeName.equals("prototype")) { //$NON-NLS-1$
StringBuilder prototype = new StringBuilder();
++prototypeCount;
if (prototypeCount == 1) {
buffer.append(" (");
} else {
buffer.append(" <B>or</B> "); //$NON-NLS-2$
buffer.append(name);
buffer.append(" (<I>"); //$NON-NLS-2$
}
NodeList varList = n.getChildNodes();
for (int j = 0; j < varList.getLength(); ++j) {
Node v = varList.item(j);
String vnodeName = v.getNodeName();
if (vnodeName.equals("parameter")) { //$NON-NLS-1$
NamedNodeMap parms = v.getAttributes();
Node parmNode = parms.item(0);
String parm = parmNode.getNodeValue();
if (prototype.length() == 0)
prototype.append(parm);
else
prototype.append(", ").append(parm);
}
}
buffer.append(prototype).append("</I>)<br>"); //$NON-NLS-1$
}
if (nodeName.equals("synopsis")) { //$NON-NLS-1$
Node textNode = n.getLastChild();
buffer.append("<br><B>Synopsis:</B> ");
buffer.append(textNode.getNodeValue());
}
}
}
}
if (buffer.length() > 0) {
HTMLPrinter.insertPageProlog(buffer, 0);
HTMLPrinter.addPageEpilog(buffer);
return buffer.toString();
}
return null;
}
public static AutoconfMacro[] getMacroList(AutoconfEditor editor) {
IEditorInput input = editor.getEditorInput();
AutotoolsHoverDoc hoverdoc = getHoverDoc(input);
return getMacroList(hoverdoc);
}
private static AutoconfMacro[] getMacroList(AutotoolsHoverDoc hoverdoc) {
if (acHoverMacros == null) {
acHoverMacros = new HashMap<>();
}
ArrayList<AutoconfMacro> masterList = new ArrayList<>();
Document[] doc = hoverdoc.getDocuments();
for (int ix = 0; ix < doc.length; ++ix) {
Document macroDoc = doc[ix];
ArrayList<AutoconfMacro> list = acHoverMacros.get(macroDoc);
if (list == null && macroDoc != null) {
list = new ArrayList<>();
NodeList nl = macroDoc.getElementsByTagName("macro"); //$NON-NLS-1$
for (int i = 0; i < nl.getLength(); ++i) {
Node macro = nl.item(i);
NamedNodeMap macroAttrs = macro.getAttributes();
Node n2 = macroAttrs.getNamedItem("id"); //$NON-NLS-1$
if (n2 != null) {
String name = n2.getNodeValue();
StringBuilder parms = new StringBuilder();
NodeList macroChildren = macro.getChildNodes();
for (int j = 0; j < macroChildren.getLength(); ++j) {
Node x = macroChildren.item(j);
if (x.getNodeName().equals("prototype")) { //$NON-NLS-1$
// Use parameters for context info.
NodeList parmList = x.getChildNodes();
int parmCount = 0;
for (int k = 0; k < parmList.getLength(); ++k) {
Node n3 = parmList.item(k);
if (n3.getNodeName().equals("parameter")) { //$NON-NLS-1$
NamedNodeMap parmVals = n3.getAttributes();
Node parmVal = parmVals.item(0);
if (parmCount > 0)
parms = parms.append(", "); //$NON-NLS-1$
parms.append(parmVal.getNodeValue());
++parmCount;
}
}
}
}
AutoconfMacro m = new AutoconfMacro(name, parms.toString());
list.add(m);
}
}
// Cache the arraylist of macros for later usage.
acHoverMacros.put(macroDoc, list);
}
if (list != null)
masterList.addAll(list);
}
// Convert to a sorted array of macros and return result.
AutoconfMacro[] macros = new AutoconfMacro[masterList.size()];
masterList.toArray(macros);
Arrays.sort(macros);
return macros;
}
public static AutoconfPrototype getPrototype(String name, AutoconfEditor editor) {
IEditorInput input = editor.getEditorInput();
AutotoolsHoverDoc hoverdoc = getHoverDoc(input);
AutoconfPrototype x = getPrototype(name, hoverdoc.getAcDocument());
if (x == null)
x = getPrototype(name, hoverdoc.getAmDocument());
return x;
}
private static AutoconfPrototype getPrototype(String name, Document document) {
AutoconfPrototype p = null;
if (document != null && name != null) {
Element elem = document.getElementById(name);
if (null != elem) {
int prototypeCount = -1;
p = new AutoconfPrototype();
p.setName(name);
NodeList nl = elem.getChildNodes();
for (int i = 0; i < nl.getLength(); ++i) {
Node n = nl.item(i);
String nodeName = n.getNodeName();
if (nodeName.equals("prototype")) { //$NON-NLS-1$
++prototypeCount;
int parmCount = 0;
int minParmCount = -1;
p.setNumPrototypes(prototypeCount + 1);
NodeList varList = n.getChildNodes();
for (int j = 0; j < varList.getLength(); ++j) {
Node v = varList.item(j);
String vnodeName = v.getNodeName();
if (vnodeName.equals("parameter")) { //$NON-NLS-1$
++parmCount;
NamedNodeMap parms = v.getAttributes();
Node parmNode = parms.item(0);
String parm = parmNode.getNodeValue();
// Check for first optional parameter which means
// we know the minimum number of parameters needed.
if (minParmCount < 0 && (parm.charAt(0) == '[' ||
parm.startsWith("...")))
minParmCount = parmCount - 1;
// Old style documentation sometimes had '[' in
// prototypes so look for one at end of a parm too.
else if (minParmCount < 0 && parm.endsWith("["))
minParmCount = parmCount;
p.setParmName(prototypeCount, parmCount - 1, parm);
}
}
p.setMaxParms(prototypeCount, parmCount);
// If we see no evidence of optional parameters, then
// the min and max number of parameters are equal.
if (minParmCount < 0)
minParmCount = parmCount;
p.setMinParms(prototypeCount, minParmCount);
}
}
}
}
return p;
}
@Override
public String getHoverInfo(ITextViewer textViewer, IRegion hoverRegion) {
String hoverInfo = null;
IDocument d = textViewer.getDocument();
try {
String name = d.get(hoverRegion.getOffset(), hoverRegion.getLength());
hoverInfo = getIndexedInfo(name, fEditor);
} catch (BadLocationException e) {
// do nothing
}
return hoverInfo;
}
@Override
public IRegion getHoverRegion(ITextViewer textViewer, int offset) {
if (textViewer != null) {
/*
* If the hover offset falls within the selection range return the
* region for the whole selection.
*/
Point selectedRange = textViewer.getSelectedRange();
if (selectedRange.x >= 0 && selectedRange.y > 0
&& offset >= selectedRange.x
&& offset <= selectedRange.x + selectedRange.y)
return new Region(selectedRange.x, selectedRange.y);
else {
return CWordFinder.findWord(textViewer.getDocument(), offset);
}
}
return null;
}
/*
* @see ITextHoverExtension#getHoverControlCreator()
* @since 3.0
*/
@Override
public IInformationControlCreator getHoverControlCreator() {
return parent -> new DefaultInformationControl(parent, getTooltipAffordanceString(),
new HTMLTextPresenter(false));
}
/*
* Static member function to allow content assist to add hover help.
*/
public static IInformationControlCreator getInformationControlCreator() {
return parent -> new DefaultInformationControl(parent, getTooltipAffordanceString(),
new HTMLTextPresenter(false));
}
protected static String getTooltipAffordanceString() {
if (fBindingService == null)
return null;
String keySequence= fBindingService.getBestActiveBindingFormattedFor(IAutotoolEditorActionDefinitionIds.SHOW_TOOLTIP);
if (keySequence == null)
return null;
return HoverMessages.getFormattedString("ToolTipFocus", keySequence); //$NON-NLS-1$
}
/**
* Returns the style sheet.
*
* @since 3.2
*/
protected static String getStyleSheet() {
if (fgStyleSheet == null) {
Bundle bundle= Platform.getBundle(AutotoolsUIPlugin.PLUGIN_ID);
URL styleSheetURL= bundle.getEntry("/AutoconfHoverStyleSheet.css"); //$NON-NLS-1$
if (styleSheetURL != null) {
try {
styleSheetURL= FileLocator.toFileURL(styleSheetURL);
try (BufferedReader reader= new BufferedReader(new InputStreamReader(styleSheetURL.openStream()))) {
StringBuilder buffer= new StringBuilder(200);
String line= reader.readLine();
while (line != null) {
buffer.append(line);
buffer.append('\n');
line= reader.readLine();
}
fgStyleSheet= buffer.toString();
}
} catch (IOException ex) {
AutotoolsUIPlugin.log(ex);
fgStyleSheet= ""; //$NON-NLS-1$
}
}
}
return fgStyleSheet;
}
}