/** * Aptana Studio * Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the GNU Public License (GPL) v3 (with exceptions). * Please see the license.html included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ package com.aptana.editor.php.internal.contentAssist; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org2.eclipse.php.internal.core.IPHPEplCoreConstants; import org2.eclipse.php.internal.core.documentModel.phpElementData.IPHPDocBlock; import org2.eclipse.php.internal.core.documentModel.phpElementData.IPHPDocTag; import com.aptana.core.util.StringUtil; import com.aptana.editor.php.epl.PHPEplPlugin; import com.aptana.editor.php.internal.indexer.language.PHPBuiltins; import com.aptana.editor.php.internal.parser.nodes.IPHPParseNode; import com.aptana.editor.php.internal.parser.nodes.PHPFunctionParseNode; public class ContentAssistUtils { /** * Maximum symbols in documentation line. */ private static final int MAX_SYMBOLS_IN_DOC_LINE = 80; /** * Minimum delta between the truncation position and previous <br> tag when we suggest truncating before the * <br> */ private static final int BR_DELTA = 2; /** * Built-in info * * @author Denis Denisenko */ public static class BuiltinInfo { public Object builtIn; public String lowerCaseName; } /** * Index of built-ins. Key is the first name character. */ private static Map<Character, List<BuiltinInfo>> index = null; private static Boolean filterByNamespace; /** * Clean the PHP built-ins content assist index. */ public static void cleanIndex() { index = null; } // Patterns used for stripping HTML in case there is no support for a CustomBrowserInformationControl on the system private static final Map<Pattern, String> htmlStrippingMap; static { htmlStrippingMap = new HashMap<Pattern, String>(); htmlStrippingMap.put(Pattern.compile("<b>|</b>"), StringUtil.EMPTY); //$NON-NLS-1$ htmlStrippingMap.put(Pattern.compile("<p>|</p>"), "\n"); //$NON-NLS-1$ //$NON-NLS-2$ htmlStrippingMap.put(Pattern.compile("<br>|</br>"), "\n"); //$NON-NLS-1$ //$NON-NLS-2$ htmlStrippingMap.put(Pattern.compile("<"), "<"); //$NON-NLS-1$ //$NON-NLS-2$ htmlStrippingMap.put(Pattern.compile(">"), ">"); //$NON-NLS-1$ //$NON-NLS-2$ } /** * @param name * @param eq * @return list of model element that matches to given name */ public synchronized static List<Object> selectModelElements(String name, boolean eq) { if (index == null || index.isEmpty()) { Collection<Object> builtins = PHPBuiltins.getInstance().getBuiltins(); if (builtins == null || PHPBuiltins.getInstance().isInitializing()) { // The built-ins are loading now return null; } initializeBuiltinsIndex(builtins); } if (name == null) { return null; } List<Object> toReturn = new ArrayList<Object>(); if (name.length() == 0) { toReturn = new ArrayList<Object>(); toReturn.addAll(PHPBuiltins.getInstance().getBuiltins()); return toReturn; } String lowerCaseName = name.toLowerCase(); Character firstCharacter = lowerCaseName.charAt(0); if (eq) { List<BuiltinInfo> lst = index.get(firstCharacter); if (lst != null) { for (BuiltinInfo info : lst) { if (acceptsNodeEquals(info, lowerCaseName)) { toReturn.add(info.builtIn); } } } } else { List<BuiltinInfo> lst = index.get(firstCharacter); if (lst != null) { for (BuiltinInfo info : lst) { if (acceptsNodeStartingWith(info, lowerCaseName)) { toReturn.add(info.builtIn); } } } } return toReturn; } /** * Gets whether node is acceptable cause starting with a name. * * @param info * - info. * @param name * - lower case name. * @return true if acceptable, false otherwise */ public static boolean acceptsNodeStartingWith(BuiltinInfo info, String name) { return info.lowerCaseName.startsWith(name); } /** * Gets whether node is acceptable cause equal to a name. * * @param info * - info. * @param name * - lower case name. * @return true if acceptable, false otherwise */ public static boolean acceptsNodeEquals(BuiltinInfo info, String name) { return info.lowerCaseName.equals(name); } /** * Truncates documentation line putting "..." in the end. * * @param line * - documentation line. * @return truncated line */ public static String truncateLineIfNeeded(String line) { if (line.length() > MAX_SYMBOLS_IN_DOC_LINE) { // ending pos is max symbols minus 3 ("..."). int endPos = MAX_SYMBOLS_IN_DOC_LINE - 3; // checking if we have <br> nearby the ending position and modifying endPos accordinly Pattern pattern = Pattern.compile("<br>"); //$NON-NLS-1$ Matcher matcher = pattern.matcher(line); while (matcher.find()) { int start = matcher.start(); int end = matcher.end(); if (start <= endPos && end >= endPos) { // if we have ending position inside the <br>, making ending position to point to the previous // symbol before the start of <br> endPos = start - 1; break; } else if (start > endPos) { // this <br> is already after the ending position, breaking the search break; } else if (end >= endPos - BR_DELTA) { endPos = start - 1; break; } } if (endPos < 0) { endPos = 0; } String subString = line.substring(0, endPos); return subString + "..."; //$NON-NLS-1$ } return line; } /** * @param node * @param name * @return documentation for given AST node */ public static String getDocumentation(IPHPParseNode node, String name) { String additionalInfo = Messages.ContentAssistUtils_noAvailableDocumentation; StringBuffer buf = new StringBuffer(); IPHPDocBlock documentation = node.getDocumentation(); if (!(node instanceof PHPFunctionParseNode)) { buf.append("<b>" + name + "</b><br>"); //$NON-NLS-1$ //$NON-NLS-2$ } else { buf.append("<b>" + ((PHPFunctionParseNode) node).getSignature() + "</b><br>"); //$NON-NLS-1$ //$NON-NLS-2$ } if (documentation != null) { String longDescription = documentation.getLongDescription(); if (longDescription.length() > 0) { buf.append(longDescription); } else { buf.append(documentation.getShortDescription()); } IPHPDocTag[] tagsAsArray = documentation.getTagsAsArray(); buf.append("<br>"); //$NON-NLS-1$ for (int a = 0; a < tagsAsArray.length; a++) { buf.append("<br>"); //$NON-NLS-1$ buf.append(tagsAsArray[a].toString()); } } else { buf.append(additionalInfo); } additionalInfo = buf.toString(); return additionalInfo; } /** * Strip out, or replace with regular ascii characters, any 'b', 'br', 'lt' or 'gt' tags. * * @param content * @return */ public static String stripBasicHTML(String content) { for (Pattern strippingPattern : htmlStrippingMap.keySet()) { content = strippingPattern.matcher(content).replaceAll(htmlStrippingMap.get(strippingPattern)); } return content; } /** * Returns <code>true</code> in case the preferences are set to filter the content assist by looking at the call * namespace; <code>false</code> otherwise. */ public static boolean isFilterByNamespace() { // This method is called very frequently, so we add a preferences listener that will exist through the entire // Studio session and will monitor any change in the STRICT_NS_ASSIST property. if (filterByNamespace == null) { filterByNamespace = PHPEplPlugin.getDefault().getPreferenceStore() .getBoolean(IPHPEplCoreConstants.STRICT_NS_CODE_ASSIST); // add a listener for the STRICT_NS_ASSIST property PHPEplPlugin.getDefault().getPreferenceStore().addPropertyChangeListener(new IPropertyChangeListener() { public void propertyChange(PropertyChangeEvent event) { if (IPHPEplCoreConstants.STRICT_NS_CODE_ASSIST.equals(event.getProperty())) { filterByNamespace = (Boolean) event.getNewValue(); } } }); } return filterByNamespace; } /** * Initializes built-in index. * * @param builtins * - built-ins. */ private static void initializeBuiltinsIndex(Collection<Object> builtins) { index = new HashMap<Character, List<BuiltinInfo>>(); for (Object builtin : builtins) { if (builtin instanceof IPHPParseNode) { String lowercaseName = ((IPHPParseNode) builtin).getNodeName().toLowerCase(); if (lowercaseName.length() != 0) { Character firstCharacter = lowercaseName.charAt(0); List<BuiltinInfo> charList = index.get(firstCharacter); if (charList == null) { charList = new ArrayList<BuiltinInfo>(); index.put(firstCharacter, charList); } BuiltinInfo info = new BuiltinInfo(); info.builtIn = builtin; info.lowerCaseName = lowercaseName; charList.add(info); } } } } }