/**
* This file Copyright (c) 2005-2008 Aptana, Inc. This program is
* dual-licensed under both the Aptana Public License and the GNU General
* Public license. You may elect to use one or the other of these licenses.
*
* This program is distributed in the hope that it will be useful, but
* AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or
* NONINFRINGEMENT. Redistribution, except as permitted by whichever of
* the GPL or APL you select, is prohibited.
*
* 1. For the GPL license (GPL), you can redistribute and/or modify this
* program under the terms of the GNU General Public License,
* Version 3, as published by the Free Software Foundation. You should
* have received a copy of the GNU General Public License, Version 3 along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Aptana provides a special exception to allow redistribution of this file
* with certain other free and open source software ("FOSS") code and certain additional terms
* pursuant to Section 7 of the GPL. You may view the exception and these
* terms on the web at http://www.aptana.com/legal/gpl/.
*
* 2. For the Aptana Public License (APL), this program and the
* accompanying materials are made available under the terms of the APL
* v1.0 which accompanies this distribution, and is available at
* http://www.aptana.com/legal/apl/.
*
* You may view the GPL, Aptana's exception and additional terms, and the
* APL in the file titled license.html at the root of the corresponding
* plugin containing this source file.
*
* Any modifications to this file must keep this entire header intact.
*/
package com.aptana.ide.editor.html.contentassist;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.swing.filechooser.FileSystemView;
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.Path;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.contentassist.ContextInformation;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.jface.text.contentassist.IContextInformationValidator;
import org.eclipse.swt.graphics.Image;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.FileEditorInput;
import org.jaxen.JaxenException;
import org.jaxen.XPath;
import com.aptana.ide.core.FileUtils;
import com.aptana.ide.core.IdeLog;
import com.aptana.ide.core.KeyValuePair;
import com.aptana.ide.core.PluginUtils;
import com.aptana.ide.core.StringUtils;
import com.aptana.ide.core.ui.CoreUIUtils;
import com.aptana.ide.editor.css.CSSLanguageEnvironment;
import com.aptana.ide.editor.css.contentassist.CSSContentAssistProcessor;
import com.aptana.ide.editor.html.HTMLLanguageEnvironment;
import com.aptana.ide.editor.html.HTMLOffsetMapper;
import com.aptana.ide.editor.html.HTMLPlugin;
import com.aptana.ide.editor.html.lexing.HTMLTokenTypes;
import com.aptana.ide.editor.html.parsing.HTMLDocumentType;
import com.aptana.ide.editor.html.parsing.HTMLMimeType;
import com.aptana.ide.editor.html.parsing.HTMLParseState;
import com.aptana.ide.editor.html.parsing.HTMLUtils;
import com.aptana.ide.editor.html.preferences.IPreferenceConstants;
import com.aptana.ide.editor.js.JSPlugin;
import com.aptana.ide.editors.UnifiedEditorsPlugin;
import com.aptana.ide.editors.unified.EditorFileContext;
import com.aptana.ide.editors.unified.IFileLanguageService;
import com.aptana.ide.editors.unified.contentassist.CodeAssistExpression;
import com.aptana.ide.editors.unified.contentassist.UnifiedContentAssistProcessor;
import com.aptana.ide.lexer.Lexeme;
import com.aptana.ide.lexer.LexemeList;
import com.aptana.ide.lexer.TokenCategories;
import com.aptana.ide.metadata.ElementMetadata;
import com.aptana.ide.metadata.EventMetadata;
import com.aptana.ide.metadata.FieldMetadata;
import com.aptana.ide.metadata.IMetadataEnvironment;
import com.aptana.ide.metadata.MetadataEnvironment;
import com.aptana.ide.metadata.ValueMetadata;
import com.aptana.ide.parsing.IOffsetMapper;
import com.aptana.ide.parsing.IParseState;
import com.aptana.ide.parsing.nodes.IParseNode;
import com.aptana.ide.parsing.nodes.ParseNodeAttribute;
import com.aptana.ide.parsing.xpath.ParseNodeXPath;
import com.aptana.ide.server.jetty.server.HTMLContextRootUtils;
/**
*
*/
public class HTMLContentAssistProcessor extends UnifiedContentAssistProcessor implements IContentAssistProcessor
{
private IContextInformationValidator validator;
private static Map<String, String> entities;
// icons
private static Image fIconField = UnifiedEditorsPlugin.getImage("icons/field_public.gif"); //$NON-NLS-1$
private static Image fIconFieldGuess = UnifiedEditorsPlugin.getImage("icons/field_public_guess.gif"); //$NON-NLS-1$
private static Image fIconTag = UnifiedEditorsPlugin.getImage("icons/html_tag.gif"); //$NON-NLS-1$
private static Image fIconTagGuess = UnifiedEditorsPlugin.getImage("icons/html_tag_guess.gif"); //$NON-NLS-1$
private static Image fIconEvent = UnifiedEditorsPlugin.getImage("icons/event.gif"); //$NON-NLS-1$
private static Image fIconFile = UnifiedEditorsPlugin.getImage("icons/file.gif"); //$NON-NLS-1$
private static Image fIconFolder = UnifiedEditorsPlugin.getImage("icons/folder.gif"); //$NON-NLS-1$
// private static Image fIconFirefox = UnifiedEditorsPlugin.getImage("icons/firefox_icon.gif");
// private static Image fIconIE = UnifiedEditorsPlugin.getImage("icons/ie_icon.gif");
private HTMLCompletionProposalComparator contentAssistComparator;
private String AUTO_ADDED = "Auto-added from environment"; //$NON-NLS-1$
/**
* ERROR indicates we are in some error state and we just escape.
*/
public static String ERROR = "ERROR"; //$NON-NLS-1$
/**
* OUTSIDE_ELEMENT indicates we are outside a tag. This allows suggestion of any possible tag names (not looking at
* document semantics yet.
*/
public static String OUTSIDE_ELEMENT = "OUTSIDE_ELEMENT"; //$NON-NLS-1$
/**
* INSIDE_OPEN_ELEMENT indicates we are between a < and a > Here we show the full list of HTML properties, or
* filtered by what is already typed
*/
public static String INSIDE_OPEN_ELEMENT = "INSIDE_OPEN_ELEMENT"; //$NON-NLS-1$
/**
* INSIDE_OPEN_ELEMENT indicates we are between a </ and a > Here we basically the "close" tag version of what the
* immediately prior open tag is
*/
public static String INSIDE_END_TAG = "INSIDE_END_TAG"; //$NON-NLS-1$
private EditorFileContext context;
private IMetadataEnvironment environment;
private Hashtable<String, ArrayList<String>> additionalProposals = new Hashtable<String, ArrayList<String>>();
/**
* Provides code assist information for HTML.
*
* @param context
*/
public HTMLContentAssistProcessor(EditorFileContext context)
{
this.context = context;
this.environment = (IMetadataEnvironment) HTMLLanguageEnvironment.getInstance().getRuntimeEnvironment();
}
/**
* @see com.aptana.ide.editors.unified.contentassist.UnifiedContentAssistProcessor#computeInnerCompletionProposals(org.eclipse.jface.text.ITextViewer,
* int, int, com.aptana.ide.lexer.LexemeList, char, char)
*/
public ICompletionProposal[] computeInnerCompletionProposals(ITextViewer viewer, int offset, int position,
LexemeList lexemeList, char activationChar, char previousChar)
{
additionalProposals.clear();
// Tricky bug. If we are Ctrl + Space in a document that has
// not been typed in yet, a current lexeme will not have been calculated
HTMLContextLocation currentLocation = getLocation(offset, lexemeList);
String tagPrefix = currentLocation.getTagName();
ICompletionProposal[] result = null;
// if we are inside a closing tag, just show the closure completions
// rather than trying to guess the tag, just show the list of
// all tags again
if (unifiedViewer != null && unifiedViewer.isHotkeyActivated())
{
unifiedViewer.setHotkeyActivated(false);
activationChar = DEFAULT_CHARACTER;
}
if (position < 0)
{
return null;
}
Lexeme currentLexeme = lexemeList.get(position);
// if we are in the end tag, just complete
if (currentLocation.getLocation().equals(ERROR))
{
return result;
}
// if we are in the end tag, just complete
if (currentLocation.getLocation().equals(INSIDE_END_TAG) && currentLocation.getAttributes().size() == 0)
{
return getTagCompletionProposals(tagPrefix, previousChar, offset, currentLexeme, lexemeList);
}
// if we are outside tag, don't show anything
if (currentLocation.getLocation().equals(OUTSIDE_ELEMENT) && previousChar == '/')
{
return getTagCompletionProposals("", activationChar, offset, currentLexeme, lexemeList); //$NON-NLS-1$
}
// if we are outside tag, don't show anything, unless we have typed "<"
if (currentLocation.getLocation().equals(OUTSIDE_ELEMENT)
&& (activationChar == '<' || activationChar == DEFAULT_CHARACTER))
{
if (currentLexeme != null && currentLexeme.typeIndex == HTMLTokenTypes.TEXT
&& currentLexeme.getText().trim().startsWith("&"))
{
result = computeHTMLEntityProposals(currentLexeme, currentLexeme.getText()).values().toArray(
new ICompletionProposal[0]);
setSelection(currentLexeme.getText(), result);
return result;
}
return getTagCompletionProposals("", previousChar, offset, currentLexeme, lexemeList); //$NON-NLS-1$
}
if (currentLocation.getLocation().equals(OUTSIDE_ELEMENT) && activationChar == '&')
{
String prefix = currentLexeme.getText();
int length = offset - currentLexeme.getStartingOffset();
if (length <= 0)
{
prefix = "";
}
else if (length <= prefix.length())
{
prefix = prefix.substring(0, length);
}
result = computeHTMLEntityProposals(currentLexeme, prefix).values().toArray(
new ICompletionProposal[0]);
setSelection(currentLexeme.getText(), result);
return result;
}
ArrayList<KeyValuePair> attributes = currentLocation.getAttributes();
String attributePrefix = null;
String valuePrefix = null;
if (attributes.size() > 0)
{
for (KeyValuePair kvp : attributes)
{
if (kvp.getLocation() == currentLexeme.offset)
{
attributePrefix = (String) kvp.getKey();
valuePrefix = (String) kvp.getValue();
break;
}
}
}
// if we are inside an open element
if (currentLocation.getLocation().equals(INSIDE_OPEN_ELEMENT))
{
// If the previous char is a " ", we are now into attributes
if ((previousChar == ' ' || previousChar == '\t') && tagPrefix != null)
{
// We don't want to show arg assist inside a string with spaces.
// However, our cursor actually has to be _inside_ the lexeme
// text to bail
if (HTMLUtils.insideQuotedString(currentLexeme, offset))
{
return null;
}
attributePrefix = ""; //$NON-NLS-1$
if (currentLexeme.getCategoryIndex() != TokenCategories.ERROR)
{
valuePrefix = null;
}
}
// if tagPrefix == "", or attributePrefix == null
// we are finishing up an open tag, or we might have backtracked
// into one we already finished
if (attributePrefix == null || currentLexeme.typeIndex == HTMLTokenTypes.START_TAG
&& currentLexeme.containsOffset(offset))
{
// What if we're inside a "style" attribute?
KeyValuePair stylePair = get(attributes, "style");
if (stylePair != null
&& (offset > stylePair.getLocation() && offset <= (stylePair.getLocation() + 2 + ((String) stylePair
.getValue()).length())))
{
// We're inside "style" attribute value!
return new CSSContentAssistProcessor(context).computeInnerCompletionProposals(viewer, offset,
position, lexemeList, activationChar, previousChar);
}
result = getTagCompletionProposals(tagPrefix, previousChar, offset, currentLexeme, lexemeList);
}
// if valuePrefix != we are starting a new value
else if (valuePrefix != null)
{
// Hack fix for align=/> (it would delete the /)
if (valuePrefix.equals("/")) //$NON-NLS-1$
{
valuePrefix = ""; //$NON-NLS-1$
}
result = getAttributeValueCompletionProposals(tagPrefix, attributePrefix, valuePrefix, offset);
String strippedValue = StringUtils.trimStringQuotes(valuePrefix);
setSelection(strippedValue, result);
}
// otherwise, we are adding attributes
else
{
Lexeme startTag = HTMLUtils.getTagOpenLexeme(currentLexeme, lexemeList);
Lexeme endTag = HTMLUtils.getTagCloseLexeme(currentLexeme, lexemeList);
Map<String, String> attribs = HTMLUtils.gatherAttributes(startTag, endTag, lexemeList);
boolean attributeQuoted = attribs.get(attributePrefix) == null ? false : true;
result = getAttributeCompletionProposals(tagPrefix, attributePrefix, previousChar, attributes,
attributeQuoted, offset);
setSelection(attributePrefix.toLowerCase(), result);
}
}
return result;
}
private KeyValuePair get(ArrayList<KeyValuePair> attributes, String key)
{
for (KeyValuePair keyValuePair : attributes)
{
if (keyValuePair.getKey().equals(key))
return keyValuePair;
}
return null;
}
/**
* getAttributeValueCompletionProposals
*
* @param tagPrefix2
* @param attributePrefix2
* @param valuePrefix
* @param offset
* @return ICompletionProposal[]
*/
public ICompletionProposal[] getAttributeValueCompletionProposals(String tagPrefix2, String attributePrefix2,
String valuePrefix, int offset)
{
if (tagPrefix2 == null || attributePrefix2 == null)
{
return null;
}
String strippedValue = StringUtils.trimStringQuotes(valuePrefix);
ArrayList<ICompletionProposal> completionProposals = new ArrayList<ICompletionProposal>();
ArrayList<String> addedFields = new ArrayList<String>();
// Global fields are the list of "all" fields
Hashtable<String, FieldMetadata> fields = environment.getGlobalFields();
String tagNameLower = tagPrefix2.toLowerCase();
String propertyNameLower = attributePrefix2.toLowerCase();
String valueNameLower = strippedValue.toLowerCase();
int beginOffset = getOffsetForInsertion(getHTMLOffsetMapper().getCurrentLexeme(), offset);
int replaceLength = valueNameLower.length();
Lexeme curLexeme = getHTMLOffsetMapper().getCurrentLexeme();
if (curLexeme.getCategoryIndex() == TokenCategories.ERROR)
{
beginOffset = curLexeme.offset;
}
if (valuePrefix.startsWith("\"") || valuePrefix.startsWith("'")) //$NON-NLS-1$ //$NON-NLS-2$
{
beginOffset++;
}
/*
* else if(valuePrefix.startsWith("\"") || valuePrefix.startsWith("'") ) { beginOffset = beginOffset -
* (valuePrefix.length() - 1); replaceLength = valuePrefix.length() - 1; }
*/
int sortingType = HTMLCompletionProposalComparator.OBJECT_TYPE_PROPERTY;
ElementMetadata em = environment.getElement(tagNameLower);
FieldMetadata fm = null;
if (em == null)
{
fm = fields.get(propertyNameLower);
}
else
{
fm = (FieldMetadata) em.getFields().get(propertyNameLower);
}
if (fm != null)
{
ICompletionProposal[] props = getFieldMetadataCompletionProposals(em, fm, beginOffset, replaceLength,
sortingType);
completionProposals.addAll(Arrays.asList(props));
}
try
{
if (propertyNameLower.equals("src")) //$NON-NLS-1$
{
ICompletionProposal[] fileProps = getFilePathCompletionProposals(valuePrefix, beginOffset,
replaceLength, sortingType);
completionProposals.addAll(Arrays.asList(fileProps));
}
}
catch (Exception ex)
{
IdeLog.logError(JSPlugin.getDefault(), Messages.HTMLContentAssistProcessor_ErrorComputingFilePath, ex);
}
try
{
if (propertyNameLower.equals("class")) //$NON-NLS-1$
{
ICompletionProposal[] fileProps = getCSSClassCompletionProposals(valuePrefix, beginOffset,
replaceLength, sortingType);
completionProposals.addAll(Arrays.asList(fileProps));
}
else if (propertyNameLower.equals("id")) //$NON-NLS-1$
{
ICompletionProposal[] fileProps = getCSSIdCompletionProposals(valuePrefix, beginOffset,
replaceLength, sortingType);
completionProposals.addAll(Arrays.asList(fileProps));
}
}
catch (Exception ex)
{
IdeLog.logError(JSPlugin.getDefault(), Messages.HTMLContentAssistProcessor_ErrorComputingFilePath, ex);
}
ICompletionProposal[] xPathProps = getXPathCompletionProposals(propertyNameLower, beginOffset, replaceLength,
sortingType);
completionProposals.addAll(Arrays.asList(xPathProps));
if (additionalProposals.containsKey(propertyNameLower))
{
ArrayList<String> addl = additionalProposals.get(fm.getName());
for (int i = 0; i < addl.size(); i++)
{
String s = (String) addl.get(i);
String trimmedValue = StringUtils.trimStringQuotes(StringUtils.trimStringQuotes(s));
if (trimmedValue.equals("")) //$NON-NLS-1$
{
continue;
}
String replaceString = trimmedValue;
String displayString = trimmedValue;
int cursorPosition = replaceString.length();
HTMLCompletionProposal cp = new HTMLCompletionProposal(replaceString, beginOffset, replaceLength,
cursorPosition, fIconFieldGuess, displayString, null, "<b>" + trimmedValue + "</b><br>" //$NON-NLS-1$ //$NON-NLS-2$
+ AUTO_ADDED, sortingType, unifiedViewer, null);
if (cp != null && !addedFields.contains(s))
{
addedFields.add(s);
completionProposals.add(cp);
}
}
}
ICompletionProposal[] result = completionProposals.toArray(new ICompletionProposal[completionProposals.size()]);
Arrays.sort(result, getProposalComparator());
return result;
}
/**
* Returns the "location" we are currently in. What this means in the end is that it helps us figure out which of
* three states we are in. This also sets the current name hash (which should be moved to HTMLOffsetMapper
* eventually)
*
* @param offset
* The current offset
* @param ll
* @return One of the location enumerations
*/
public HTMLContextLocation getLocation(int offset, LexemeList ll)
{
HTMLContextLocation cl = new HTMLContextLocation();
cl.setLocation(OUTSIDE_ELEMENT);
if (offset == 0)
{
return cl;
}
int currentLexemePosition = computeCurrentLexemeIndex(offset, ll);
KeyValuePair currentAttribute = null;
// backtrack over lexemes to find name - we are really just
// searching for the last OPEN_ELEMENT
while (currentLexemePosition >= 0)
{
Lexeme curLexeme = ll.get(currentLexemePosition);
// attributes can be NAME EQUALS STRING OR NAME EQUALS NAME
if (curLexeme.typeIndex == HTMLTokenTypes.NAME)
{
boolean isAttributeValue = false;
// If previous lexeme is an equals, we are an attribute value
if (currentLexemePosition > 0)
{
Lexeme prev = ll.get(currentLexemePosition - 1);
if (prev.typeIndex == HTMLTokenTypes.EQUAL)
{
isAttributeValue = true;
}
}
String lexemeText = curLexeme.getText();
// create a new KeyValuePair, but don't add it yet
if (isAttributeValue)
{
currentAttribute = new KeyValuePair("PLACEHOLDER", lexemeText, curLexeme.offset); //$NON-NLS-1$
}
else if (currentAttribute != null)
{
// add at beginning as we are parsing in reverse order
KeyValuePair foundAttribute = cl.find(lexemeText, currentAttribute.getLocation());
if (foundAttribute != null && foundAttribute.getLocation() != curLexeme.offset)
{
cl.getAttributes().remove(foundAttribute);
}
cl.getAttributes().add(0,
new KeyValuePair(lexemeText, currentAttribute.getValue(), currentAttribute.getLocation()));
currentAttribute = null;
}
else
{
// add at beginning as we are parsing in reverse order
KeyValuePair foundAttribute = cl.find(lexemeText, curLexeme.offset);
if (foundAttribute != null && foundAttribute.getLocation() != curLexeme.offset)
{
cl.getAttributes().remove(foundAttribute);
}
cl.getAttributes().add(0, new KeyValuePair(lexemeText, null, curLexeme.offset));
currentAttribute = null;
}
}
if (curLexeme.typeIndex == HTMLTokenTypes.EQUAL)
{
if (currentAttribute == null)
{
currentAttribute = new KeyValuePair("PLACEHOLDER", "", curLexeme.offset); //$NON-NLS-1$ //$NON-NLS-2$
}
}
if (curLexeme.typeIndex == HTMLTokenTypes.STRING)
{
currentAttribute = new KeyValuePair("PLACEHOLDER", curLexeme.getText(), curLexeme.offset); //$NON-NLS-1$
}
if (curLexeme.typeIndex == HTMLTokenTypes.START_TAG)
{
cl.setTagName(curLexeme.getText().replaceAll("<", "")); //$NON-NLS-1$ //$NON-NLS-2$
}
if (curLexeme.typeIndex == HTMLTokenTypes.END_TAG)
{
cl.setTagName(curLexeme.getText().replaceAll("</", "")); //$NON-NLS-1$ //$NON-NLS-2$
cl.setLocation(INSIDE_END_TAG);
break;
}
if (curLexeme.typeIndex == HTMLTokenTypes.GREATER_THAN)
{
cl.setLocation(OUTSIDE_ELEMENT);
break;
}
if (curLexeme.typeIndex == HTMLTokenTypes.ERROR && curLexeme.getText().equals("/")) //$NON-NLS-1$
{
cl.setLocation(ERROR);
break;
}
if (curLexeme.typeIndex == HTMLTokenTypes.SLASH_GREATER_THAN
&& (curLexeme.containsOffset(offset) || offset >= curLexeme.getEndingOffset()))
{
cl.setLocation(OUTSIDE_ELEMENT);
break;
}
if (curLexeme.typeIndex == HTMLTokenTypes.START_TAG || curLexeme.typeIndex == HTMLTokenTypes.ERROR
&& curLexeme.getText().equals("<")) //$NON-NLS-1$
{
cl.setLocation(INSIDE_OPEN_ELEMENT);
break;
}
currentLexemePosition--;
}
return cl;
}
/**
* The characters that triggers completion proposals (dot for completion, and space for "new XX" in our case)
*
* @return Returns the trigger characters for code completion.
*/
public char[] getCompletionProposalAutoActivationCharacters()
{
return new char[] { '<', '/', ' ', '\t', '=', '>', '&' };
}
/**
* Characters that trigger tooltip popup help
*
* @return Returns the trigger characters for auto activation.
*/
public char[] getContextInformationAutoActivationCharacters()
{
// Make context popup automatically after the following characters
return new char[] { '=' };
}
/**
* The characters that triggers competion proposals (dot for completion, and space for "new XX" in our case)
*
* @return Returns the trigger characters for code completion.
*/
public int[] getCompletionProposalSeparatorLexemes()
{
return new int[] { HTMLTokenTypes.GREATER_THAN, HTMLTokenTypes.SLASH_GREATER_THAN, HTMLTokenTypes.EQUAL,
HTMLTokenTypes.START_TAG, HTMLTokenTypes.END_TAG };
}
/**
* @see com.aptana.ide.editors.unified.contentassist.IUnifiedContentAssistProcessor#getCompletionProposalIdleActivationTokens()
*/
public int[] getCompletionProposalIdleActivationTokens()
{
return new int[] { HTMLTokenTypes.START_TAG, HTMLTokenTypes.END_TAG, HTMLTokenTypes.NAME };
}
/**
* @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#getContextInformationValidator()
*/
public IContextInformationValidator getContextInformationValidator()
{
if (validator == null)
validator = new HTMLContextInformationValidator(this);
return validator;
}
/**
* @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#getErrorMessage()
*/
public String getErrorMessage()
{
return null;
}
/**
* Gets the offset for inserting a new item into the document
*
* @param curLexeme
* @param offset
* @return The index at which to insert
*/
public static int getOffsetForInsertion(Lexeme curLexeme, int offset)
{
if (curLexeme == null)
{
return offset;
}
int beginOffset = offset;
if (curLexeme.typeIndex == HTMLTokenTypes.TEXT)
{
return beginOffset;
}
// If we're in an identifier, it's likely we'll want to replace that
// current value
if (curLexeme.getCategoryIndex() == TokenCategories.KEYWORD
|| curLexeme.getCategoryIndex() == TokenCategories.LITERAL || HTMLUtils.isStartTag(curLexeme)
|| HTMLUtils.isEndTag(curLexeme))
{
beginOffset = curLexeme.getStartingOffset();
}
return beginOffset;
}
/**
* Returns a set of valid completion proposals.
*
* @param tagPrefix
* The tag already typed (to filter on)
* @param attributePrefix
* The attribute already typed (to filter on)
* @param activationChar
* That char used to trigger the content
* @param addedAttributes
* The lsit of attributes already added. We'll remove those
* @param hasAttachedValue
* Does it already have a "value" attached at the end
* @param offset
* @return Returns an array of completion proposals.
*/
public ICompletionProposal[] getAttributeCompletionProposals(String tagPrefix, String attributePrefix,
char activationChar, ArrayList<KeyValuePair> addedAttributes, boolean hasAttachedValue, int offset)
{
if (tagPrefix == null)
{
return null;
}
Hashtable<String, HTMLCompletionProposal> completionProposals = new Hashtable<String, HTMLCompletionProposal>();
String tagPrefixLower = tagPrefix.toLowerCase();
String attributePrefixLower = attributePrefix.toLowerCase();
ElementMetadata em = environment.getElement(tagPrefixLower);
if (em == null)
{
return null;
}
int beginOffset = offset;
Lexeme curLexeme = getHTMLOffsetMapper().getCurrentLexeme();
if (curLexeme.typeIndex == HTMLTokenTypes.NAME)
{
beginOffset = curLexeme.getStartingOffset();
}
Iterator iter = em.getFields().values().iterator();
while (iter.hasNext())
{
FieldMetadata fm = (FieldMetadata) iter.next();
// if (!addedAttributes.contains(fm.getName())) {
HTMLCompletionProposal cp = createFieldProposal(attributePrefixLower, beginOffset, em, fm, hasAttachedValue);
completionProposals.put(fm.getName(), cp);
// cp.scheduleContentAssistOnInsert(true);
// }
}
Iterator iterEvents = em.getEvents().values().iterator();
while (iterEvents.hasNext())
{
EventMetadata evm = (EventMetadata) iterEvents.next();
// if (!addedAttributes.contains(evm.getName())) {
HTMLCompletionProposal cp = createEventProposal(attributePrefixLower, beginOffset, em, evm,
hasAttachedValue);
completionProposals.put(evm.getName(), cp);
// cp.scheduleContentAssistOnInsert(true);
// }
}
ICompletionProposal[] result = completionProposals.values().toArray(
new ICompletionProposal[completionProposals.size()]);
Arrays.sort(result, getProposalComparator());
return result;
}
/**
* @param attributePrefix
* @param beginOffset
* @param em
* @param fm
* @param hasAttachedValue
* Does it already have a "value" attached at the end
* @return A new completion proposal for fields
*/
public HTMLCompletionProposal createFieldProposal(String attributePrefix, int beginOffset, ElementMetadata em,
FieldMetadata fm, boolean hasAttachedValue)
{
String docText = environment.getFieldDocumentation(fm);
String replaceString = fm.getName();
String quoteString = HTMLUtils.quoteAttributeValue(getPreferenceStore(), ""); //$NON-NLS-1$
boolean insertEquals = HTMLUtils.insertEquals(getPreferenceStore());
// [IM] Don't add on '=' for the moment
if (!hasAttachedValue && insertEquals)
{
replaceString = replaceString + "=" + quoteString; //$NON-NLS-1$
}
String displayString = fm.getName();
int cursorPosition = replaceString.length();
// Place that cursor inside the attribute value location
if (!hasAttachedValue && insertEquals && quoteString.length() == 2)
{
cursorPosition--;
}
int replaceLength = attributePrefix.length();
Image[] userAgents = null;
if (fm.getUserAgents().length == 0)
{
userAgents = getUserAgentImages(getUserAgents(), em.getUserAgentPlatformNames());
}
else
{
userAgents = getUserAgentImages(getUserAgents(), fm.getUserAgentPlatformNames());
}
HTMLCompletionProposal cp = new HTMLCompletionProposal(replaceString, beginOffset, replaceLength,
cursorPosition, fIconField, displayString, null, docText,
HTMLCompletionProposalComparator.OBJECT_TYPE_PROPERTY, unifiedViewer, userAgents);
return cp;
}
/**
* @param attributePrefix
* @param beginOffset
* @param em
* @param fm
* @param hasAttachedValue
* Does it already have a "value" attached at the end
* @return A new completion proposal for events
*/
public HTMLCompletionProposal createEventProposal(String attributePrefix, int beginOffset, ElementMetadata em,
EventMetadata fm, boolean hasAttachedValue)
{
String docText = environment.getEventDocumentation(fm);
String replaceString = fm.getName();
String quoteString = HTMLUtils.quoteAttributeValue(getPreferenceStore(), ""); //$NON-NLS-1$
boolean insertEquals = HTMLUtils.insertEquals(getPreferenceStore());
// [IM] Don't add on '=' for the moment
if (!hasAttachedValue && insertEquals)
{
replaceString = replaceString + "=" + quoteString; //$NON-NLS-1$
}
String displayString = fm.getName();
int cursorPosition = replaceString.length();
int replaceLength = attributePrefix.length();
// Place that cursor inside the attribute value location, assuming we are quoting attributes
if (!hasAttachedValue && insertEquals && quoteString.length() == 2)
{
cursorPosition--;
}
Image[] userAgents = null;
if (fm.getUserAgents().length == 0)
{
userAgents = getUserAgentImages(getUserAgents(), em.getUserAgentPlatformNames());
}
else
{
userAgents = getUserAgentImages(getUserAgents(), fm.getUserAgentPlatformNames());
}
HTMLCompletionProposal cp = new HTMLCompletionProposal(replaceString, beginOffset, replaceLength,
cursorPosition, fIconEvent, displayString, null, docText,
HTMLCompletionProposalComparator.OBJECT_TYPE_PROPERTY, unifiedViewer, userAgents);
return cp;
}
/**
* Returns a set of valid completion proposals.
*
* @param prefix
* The text already typed (to filter on)
* @param activationChar
* The char used to activate the code assist
* @param offset
* @param currentLexeme
* @param lexemeList
* @return Returns an array of completion proposals.
*/
public ICompletionProposal[] getTagCompletionProposals(String prefix, char activationChar, int offset,
Lexeme currentLexeme, LexemeList lexemeList)
{
IParseState parseState = context.getParseState();
HTMLParseState htmlParseState = (HTMLParseState) parseState.getParseState(HTMLMimeType.MimeType);
Hashtable<String, HTMLCompletionProposal> completionProposals = new Hashtable<String, HTMLCompletionProposal>();
if (currentLexeme == null)
{
return null;
}
if (currentLexeme.typeIndex != HTMLTokenTypes.TEXT && !HTMLUtils.isStartTag(currentLexeme)
&& !HTMLUtils.isEndTag(currentLexeme))
{
return null;
}
// If we are in an already closed tag, then don't display code assist, unless forced
int tagClosedType = HTMLUtils.isTagClosed(currentLexeme, lexemeList);
boolean tagClosed = tagClosedType != HTMLUtils.TAG_OPEN;
// Commented out to fix bug #1211 [IM]. To fix this correctly, on auto-pop
// the activation char needs to be computed differently. For right now, we don't show
// anything if
// autopop has been set.
// IM....we actually made a change like that, and now '<' are auto-inserted.
// if (tagClosed && activationChar == DEFAULT_CHARACTER && unifiedViewer != null)
// {
// return null;
// }
String textPrefix = HTMLUtils.getOpenTagName(currentLexeme, offset);
if (tagClosed)
{
textPrefix = HTMLUtils.getOpenTagName(currentLexeme, currentLexeme.getEndingOffset());
}
String testPrefix = textPrefix.toLowerCase();
int lastAmpersand = textPrefix.lastIndexOf('&');
// We have an ampersand, and it's not followed by a semicolon or space
if (lastAmpersand > -1 && lastAmpersand > textPrefix.lastIndexOf(';')
&& lastAmpersand > textPrefix.lastIndexOf(' '))
{
testPrefix = textPrefix.substring(lastAmpersand);
completionProposals.putAll(computeHTMLEntityProposals(currentLexeme, textPrefix));
}
else
{
testPrefix = textPrefix.toLowerCase();
}
if (currentLexeme.typeIndex == HTMLTokenTypes.TEXT)
{
textPrefix = ""; //$NON-NLS-1$
}
// We need to test against lower-case items
int beginOffset = getOffsetForInsertion(currentLexeme, offset);
String[] em = environment.getAllElements();
for (int i = 0; i < em.length; i++)
{
String e = em[i];
String docText = environment.getElementDocumentation(e);
String displayString = e;
String replaceString = e;
int replaceLength = textPrefix.length();
int cursorPosition = replaceString.length();
// We are now correctly parsing the initial "<" as the start
// of a tag, so we'll want to replace that too.
if (HTMLUtils.isEndTag(currentLexeme))
{
// Strange case where ctrl + spacing in an already completed HTML tag.
// this can most likely be simplified
if (tagClosed)
{
replaceString = HTMLUtils.createOpenTag(replaceString, false);
}
else
{
replaceString = HTMLUtils.createOpenTag(replaceString, true);
}
replaceLength = currentLexeme.length;
cursorPosition = replaceString.length();
}
else
{
boolean insertClosingTags = true;
if (getPreferenceStore() != null)
{
insertClosingTags = getPreferenceStore().getBoolean(IPreferenceConstants.AUTO_INSERT_CLOSE_TAGS);
}
if (insertClosingTags)
{
int cursorOffset = 1;
boolean emptyTag = htmlParseState.isEmptyTagType(replaceString);
// [IM] Fix for 5951 to not auto-close doctype
boolean isDocType = replaceString != null && replaceString.toLowerCase().startsWith("!doctype"); //$NON-NLS-1$
String startString = null;
if (emptyTag && !tagClosed && htmlParseState.getDocumentType() >= HTMLDocumentType.XHTML_1_0_STRICT)
{
startString = HTMLUtils.createSelfClosedTag(replaceString);
cursorOffset = 2;
}
else
{
startString = HTMLUtils.createOpenTag(replaceString, !tagClosed);
}
cursorPosition = startString.length();
if (!tagClosed && !HTMLUtils.isStartTagBalanced(currentLexeme, lexemeList, htmlParseState)
&& !emptyTag && !isDocType)
{
// TODO: There is definitely a bug here with !DOCTYPE, but fixing it will
// have to wait
replaceString = startString + HTMLUtils.createCloseTag(replaceString, true);
cursorOffset = 1;
}
else
{
replaceString = startString;
}
// If the tag is already closed, we back up one char to allow to
// insert more attributes
if (!tagClosed)
{
cursorPosition = cursorPosition - cursorOffset;
}
}
else
{
replaceString = HTMLUtils.createOpenTag(replaceString, true);
cursorPosition = replaceString.length();
}
if (currentLexeme.typeIndex != HTMLTokenTypes.TEXT)
{
replaceLength++;
}
}
HTMLCompletionProposal cp = new HTMLCompletionProposal(replaceString, beginOffset, replaceLength,
cursorPosition, fIconTag, displayString, null, docText,
HTMLCompletionProposalComparator.OBJECT_TYPE_PROPERTY, unifiedViewer, getUserAgentImages(
getUserAgents(), environment.getUserAgentPlatformNames(e)));
completionProposals.put(displayString, cp);
}
boolean addedCloseTag = false;
Lexeme unclosed = HTMLUtils.getPreviousUnclosedTag(currentLexeme, lexemeList, htmlParseState);
if (unclosed != null && !HTMLUtils.isStartTagBalanced(unclosed, lexemeList, htmlParseState))
{
String displayString = HTMLUtils.stripTagEndings(unclosed.getText());
String replaceString = displayString;
boolean emptyTag = htmlParseState.isEmptyTagType(displayString);
if (!emptyTag)
{
addedCloseTag = true;
// Strange case where ctrl + spacing in an already completed HTML tag.
// this can most likely be simplified
if (tagClosed)
{
replaceString = HTMLUtils.createCloseTag(replaceString, false);
}
else
{
replaceString = HTMLUtils.createCloseTag(replaceString, true);
}
int replaceLength = getReplaceLengthByLexeme(currentLexeme, offset);
int cursorPosition = replaceString.length();
displayString = "/" + displayString; //$NON-NLS-1$
ElementMetadata e = environment.getElement(unclosed);
String docText = null;
if (e != null)
{
docText = environment.getElementDocumentation(e.getName());
}
HTMLCompletionProposal cp = new HTMLCompletionProposal(replaceString,
currentLexeme.getStartingOffset(), replaceLength, cursorPosition, fIconTag, displayString,
null, docText, HTMLCompletionProposalComparator.OBJECT_TYPE_PROPERTY, unifiedViewer,
getAllUserAgentImages(getUserAgents()));
completionProposals.put(displayString, cp);
}
}
ICompletionProposal[] result = completionProposals.values().toArray(
new ICompletionProposal[completionProposals.size()]);
Arrays.sort(result, getProposalComparator());
if (HTMLUtils.isEndTag(currentLexeme) && addedCloseTag)
{
setSelection("/" + testPrefix, result); //$NON-NLS-1$
}
else
{
setSelection(testPrefix, result);
}
return result;
}
private Map<String, HTMLCompletionProposal> computeHTMLEntityProposals(Lexeme currentLexeme, String textPrefix)
{
Map<String, HTMLCompletionProposal> completionProposals = new HashMap<String, HTMLCompletionProposal>();
Map<String, String> entityMap = getEntities();
String trimmedPrefix = StringUtils.trimStart(textPrefix);
trimmedPrefix = trimmedPrefix.substring(trimmedPrefix.lastIndexOf("&"));
for (Map.Entry<String, String> entry : entityMap.entrySet())
{
String displayString = '&' + entry.getKey() + ';';
String replaceString = displayString;
int beginOffset = currentLexeme.getStartingOffset() + textPrefix.lastIndexOf('&');
int replaceLength = textPrefix.length() - textPrefix.lastIndexOf('&');
String doc = entry.getValue();
if (!displayString.startsWith(trimmedPrefix))
continue; // Filter the proposals by prefix matching?
HTMLCompletionProposal cp = new HTMLCompletionProposal(replaceString, beginOffset, replaceLength,
replaceString.length(), fIconTag, displayString, null, doc,
HTMLCompletionProposalComparator.OBJECT_TYPE_PROPERTY, unifiedViewer, getUserAgentImages(
getUserAgents(), new String[0]));
completionProposals.put(displayString, cp);
}
return completionProposals;
}
private Map<String, String> getEntities()
{
if (entities == null)
{
entities = new HashMap<String, String>();
try
{
InputStream stream = FileLocator.openStream(HTMLPlugin.getDefault().getBundle(), new Path(
"src/com/aptana/ide/editor/html/resources/entities.properties"), false); //$NON-NLS-1$
Properties loadedEntities = new Properties();
loadedEntities.load(stream);
for (Object entityObject : loadedEntities.keySet())
{
String entity = (String) entityObject;
entities.put(entity, loadedEntities.getProperty(entity));
}
}
catch (Exception e)
{
IdeLog.logError(HTMLPlugin.getDefault(), e.getMessage(), e);
}
}
return entities;
}
/**
* Returns a reference to the current offsetMapper
*
* @return The reference to the mapper
*/
public IOffsetMapper getOffsetMapper()
{
IFileLanguageService ls = this.context.getLanguageService(HTMLMimeType.MimeType);
if (ls != null)
{
return ls.getOffsetMapper();
}
else
{
return null;
}
}
/**
* Returns a reference to the current offsetMapper
*
* @return The reference to the mapper
*/
public HTMLOffsetMapper getHTMLOffsetMapper()
{
return (HTMLOffsetMapper) getOffsetMapper();
}
/**
* @see UnifiedContentAssistProcessor#getPreferenceStore()
*/
protected IPreferenceStore getPreferenceStore()
{
if (PluginUtils.isPluginLoaded(HTMLPlugin.getDefault()))
{
return HTMLPlugin.getDefault().getPreferenceStore();
}
else
{
return null;
}
}
/**
* @see com.aptana.ide.editors.unified.contentassist.UnifiedContentAssistProcessor#computeInnerContextInformation(java.lang.String,
* int, int, com.aptana.ide.lexer.LexemeList)
*/
public IContextInformation[] computeInnerContextInformation(String documentSource, int offset, int position,
LexemeList lexemeList)
{
IContextInformation[] ici = null;
// char activationChar = getActivationChar(documentSource, offset,
// getCompletionProposalAllActivationCharacters());
HTMLContextLocation currentLocation = getLocation(offset, lexemeList);
String tagPrefix = currentLocation.getTagName();
if (tagPrefix == null)
{
return null;
}
Hashtable fields = environment.getGlobalFields();
ArrayList<KeyValuePair> attributes = currentLocation.getAttributes();
if (attributes.size() == 0)
{
return null;
}
KeyValuePair attribute = attributes.get(attributes.size() - 1);
String propertyNameLower = ((String) attribute.getKey()).toLowerCase();
FieldMetadata fm = (FieldMetadata) fields.get(propertyNameLower);
if (fm == null || fm.getValues().size() > 0)
{
return null;
}
// Right now we just print the description. This should be a list of
// arguments, like VS 2003
ContextInformation ci = new ContextInformation("contextDisplayString", fm.getDescription()); //$NON-NLS-1$
if (ci != null)
{
ici = new IContextInformation[] { ci };
}
return ici;
}
/**
* @see UnifiedContentAssistProcessor#getProposalComparator()
*/
public Comparator<ICompletionProposal> getProposalComparator()
{
if (contentAssistComparator == null)
contentAssistComparator = new HTMLCompletionProposalComparator();
return contentAssistComparator;
}
/**
* getFilePathCompletionProposals
*
* @param valuePrefix
* @param beginOffset
* @param replaceLength
* @param sortingType
*/
private ICompletionProposal[] getFilePathCompletionProposals(String valuePrefix, int beginOffset,
int replaceLength, int sortingType)
{
ArrayList<HTMLCompletionProposal> completionProposals = new ArrayList<HTMLCompletionProposal>();
Map<String, Image> ht = com.aptana.ide.core.ui.ImageUtils.fileIconsHash;
IEditorInput pathEditor = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage()
.getActiveEditor().getEditorInput();
String urlPrefix = null;
if (pathEditor instanceof FileEditorInput)
{
urlPrefix = getProjectContextRoot(pathEditor);
}
String editorPath = CoreUIUtils.getPathFromEditorInput(pathEditor);
if (urlPrefix != null && !"".equals(urlPrefix) && valuePrefix != null && valuePrefix.indexOf('/') == 1) //$NON-NLS-1$
{
editorPath = urlPrefix;
}
String currentPath = editorPath;
if (valuePrefix != null)
{
String s = StringUtils.trimStringQuotes(valuePrefix);
if (!"".equals(s)) //$NON-NLS-1$
{
File current = new File(currentPath);
if (current.isDirectory())
{
currentPath = currentPath + s;
}
else
{
currentPath = current.getParent().toString() + File.separator + s;
}
}
}
File[] files = FileUtils.getFilesInDirectory(new File(currentPath));
if (files == null)
{
return new ICompletionProposal[0];
}
for (int i = 0; i < files.length; i++)
{
File f = files[i];
if (f.getName().startsWith(".")) //$NON-NLS-1$
{
continue;
}
String fileType = ""; //$NON-NLS-1$
try
{
fileType = FileSystemView.getFileSystemView().getSystemTypeDescription(f);
}
catch (Exception ex)
{
IdeLog.logError(HTMLPlugin.getDefault(), Messages.HTMLContentAssistProcessor_TypeDescriptionError, ex);
}
// Don't include the current file in the list
if (f.toString().equals(editorPath))
{
continue;
}
Image image = null;
if (fileType != null)
{
image = ht.get(fileType);
}
if (image == null)
{
image = fIconFile;
if (f.isDirectory())
{
image = fIconFolder;
}
}
String replaceString = FileUtils.makeFilePathRelative(new File(editorPath), f);
replaceString = replaceString.replaceAll("\\\\", "/"); //$NON-NLS-1$ //$NON-NLS-2$
String displayString = CoreUIUtils.getPathFromURI(replaceString);
int cursorPosition = replaceString.length();
HTMLCompletionProposal cp = new HTMLCompletionProposal(replaceString, beginOffset, replaceLength,
cursorPosition, image, displayString, null, f.toString(), sortingType, unifiedViewer, new Image[0]);
if (cp != null)
{
completionProposals.add(cp);
}
}
return completionProposals.toArray(new ICompletionProposal[0]);
}
/**
* getCSSClassCompletionProposals
*
* @param valuePrefix
* @param beginOffset
* @param replaceLength
* @param sortingType
*/
private ICompletionProposal[] getCSSClassCompletionProposals(String valuePrefix, int beginOffset,
int replaceLength, int sortingType)
{
String trimmedValue = StringUtils.trimStringQuotes(valuePrefix);
ArrayList<HTMLCompletionProposal> completionProposals = new ArrayList<HTMLCompletionProposal>();
String path = "";
IEditorInput pathEditor = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage()
.getActiveEditor().getEditorInput();
if (pathEditor instanceof IFileEditorInput)
{
IFileEditorInput fileEI = (IFileEditorInput) pathEditor;
path = fileEI.getFile().getProject().getFullPath().toPortableString();
}
Collection<String> classes = CSSLanguageEnvironment.getInstance().getClasses(path, trimmedValue);
for (String cssClass : classes)
{
int cursorPosition = cssClass.length();
HTMLCompletionProposal cp = new HTMLCompletionProposal(cssClass, beginOffset, replaceLength,
cursorPosition, fIconTagGuess, cssClass, null, cssClass, sortingType, unifiedViewer, getAllUserAgentImages(getUserAgents()));
completionProposals.add(cp);
}
return completionProposals.toArray(new ICompletionProposal[0]);
}
/**
* getCSSIdCompletionProposals
*
* @param valuePrefix
* @param beginOffset
* @param replaceLength
* @param sortingType
*/
private ICompletionProposal[] getCSSIdCompletionProposals(String valuePrefix, int beginOffset,
int replaceLength, int sortingType)
{
String trimmedValue = StringUtils.trimStringQuotes(valuePrefix);
ArrayList<HTMLCompletionProposal> completionProposals = new ArrayList<HTMLCompletionProposal>();
String path = "";
IEditorInput pathEditor = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage()
.getActiveEditor().getEditorInput();
if (pathEditor instanceof IFileEditorInput)
{
IFileEditorInput fileEI = (IFileEditorInput) pathEditor;
path = fileEI.getFile().getProject().getFullPath().toPortableString();
}
Collection<String> ids = CSSLanguageEnvironment.getInstance().getIds(path, trimmedValue);
for (String cssId : ids)
{
int cursorPosition = cssId.length();
HTMLCompletionProposal cp = new HTMLCompletionProposal(cssId, beginOffset, replaceLength,
cursorPosition, fIconTagGuess, cssId, null, cssId, sortingType, unifiedViewer, getAllUserAgentImages(getUserAgents()));
completionProposals.add(cp);
}
return completionProposals.toArray(new ICompletionProposal[0]);
}
/**
* Get the name of the project context root
*
* @param input
* @return
* @throws CoreException
*/
private String getProjectContextRoot(IEditorInput input)
{
String urlPrefix = null;
FileEditorInput fei = (FileEditorInput) input;
IFile file = fei.getFile();
IProject project = file.getProject();
urlPrefix = HTMLContextRootUtils.resolveURL(project, "/"); //$NON-NLS-1$
urlPrefix = project.getLocation().append(urlPrefix).toOSString();
return urlPrefix;
}
/**
* getXPathCompletionProposals
*
* @param attributeName
* @param offset
* @param replaceLength
* @param sortingType
* @return ICompletionProposal[]
*/
public ICompletionProposal[] getXPathCompletionProposals(String attributeName, int offset, int replaceLength,
int sortingType)
{
ArrayList<HTMLCompletionProposal> completionProposals = new ArrayList<HTMLCompletionProposal>();
IParseNode root = context.getParseState().getRoot().getParseResults();
String editors = null;
if (getPreferenceStore() != null)
{
editors = getPreferenceStore().getString(
com.aptana.ide.editors.preferences.IPreferenceConstants.CODE_ASSIST_EXPRESSIONS);
}
CodeAssistExpression[] expressions = CodeAssistExpression.deserializeErrorDescriptors(editors);
for (int i = 0; i < expressions.length; i++)
{
CodeAssistExpression expression = expressions[i];
if (attributeName.matches(expression.getExpression()))
{
try
{
XPath xpath = new ParseNodeXPath(expression.getXPath());
Object result = xpath.evaluate(root);
if (result instanceof List)
{
List xpathResult = (List) result;
for (Iterator iter = xpathResult.iterator(); iter.hasNext();)
{
Object element = (Object) iter.next();
if (element instanceof ParseNodeAttribute)
{
ParseNodeAttribute pna = (ParseNodeAttribute) element;
String replaceString = pna.getValue();
int cursorPosition = replaceString.length();
HTMLCompletionProposal cp = new HTMLCompletionProposal(replaceString, offset,
replaceLength, cursorPosition, fIconFieldGuess, replaceString, null,
"<b>" + replaceString + "</b><br>" //$NON-NLS-1$ //$NON-NLS-2$
+ AUTO_ADDED, sortingType, unifiedViewer, null);
if (cp != null)
{
completionProposals.add(cp);
}
}
}
}
}
catch (JaxenException e)
{
IdeLog.logError(HTMLPlugin.getDefault(), e.getMessage());
}
}
}
return completionProposals.toArray(new ICompletionProposal[0]);
}
/**
* getFieldMetadataCompletionProposals
*
* @param em
* @param fm
* @param beginOffset
* @param replaceLength
* @param sortingType
* @return ICompletionProposal[]
*/
private ICompletionProposal[] getFieldMetadataCompletionProposals(ElementMetadata em, FieldMetadata fm,
int beginOffset, int replaceLength, int sortingType)
{
ArrayList<HTMLCompletionProposal> completionProposals = new ArrayList<HTMLCompletionProposal>();
for (int i = 0; i < fm.getValues().size(); i++)
{
ValueMetadata value = (ValueMetadata) fm.getValues().get(i);
// * values mean "anything". Not sure how to represent that in
// content
// assist
if (value.getName() != "*") //$NON-NLS-1$
{
String docText = ""; //$NON-NLS-1$
docText = MetadataEnvironment.getValueDocumentation(value);
// TODO Proper-case string back to what they originally had
String replaceString = value.getName();
String displayString = value.getName();
// Fixed #592. We now insert "" in place of "*"
if (replaceString.equals("*")) //$NON-NLS-1$
{
replaceString = ""; //$NON-NLS-1$
}
int cursorPosition = replaceString.length();
Image[] userAgents = null;
if (value.getUserAgents().length == 0 && fm.getUserAgents().length == 0)
{
userAgents = getUserAgentImages(getUserAgents(), em.getUserAgentPlatformNames());
}
else if (value.getUserAgents().length == 0)
{
userAgents = getUserAgentImages(getUserAgents(), fm.getUserAgentPlatformNames());
}
else
{
userAgents = getUserAgentImages(getUserAgents(), value.getUserAgentPlatformNames());
}
HTMLCompletionProposal cp = new HTMLCompletionProposal(replaceString, beginOffset, replaceLength,
cursorPosition, fIconField, displayString, null, docText, sortingType, unifiedViewer,
userAgents);
if (cp != null)
{
completionProposals.add(cp);
}
}
}
return completionProposals.toArray(new ICompletionProposal[0]);
}
/**
* Gets replace length by lexeme.
*
* @param lexeme
* - lexeme.
* @param offset
* - caret offset
* @return replace length.
*/
private int getReplaceLengthByLexeme(Lexeme lexeme, int offset)
{
// Use the minimum of the trimmed length of the lexeme and the
// length of the part of lexeme ahead of the caret offset
return Math.min(lexeme.getText().trim().length(), offset - lexeme.offset);
}
}