| /******************************************************************************* |
| * 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; |
| } |
| } |