Java tutorial
/** * 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.css.contentassist; import java.io.File; 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 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.Path; import org.eclipse.core.runtime.QualifiedName; 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.jface.text.source.SourceViewer; import org.eclipse.swt.graphics.Image; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IFileEditorInput; import org.eclipse.ui.PlatformUI; import com.aptana.ide.core.FileUtils; import com.aptana.ide.core.IdeLog; 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.CSSColors; import com.aptana.ide.editor.css.CSSLanguageEnvironment; import com.aptana.ide.editor.css.CSSOffsetMapper; import com.aptana.ide.editor.css.CSSPlugin; import com.aptana.ide.editor.css.lexing.CSSTokenTypes; import com.aptana.ide.editor.css.parsing.CSSMimeType; import com.aptana.ide.editor.css.preferences.IPreferenceConstants; 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.UnifiedCompletionProposal; 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.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; /** * */ public class CSSContentAssistProcessor extends UnifiedContentAssistProcessor implements IContentAssistProcessor { private static final String VALUE_PROPOSAL_ALL = "*"; //$NON-NLS-1$ private static final String CSS_CLASS_PREFIX = "."; //$NON-NLS-1$ private static final String CSS_ID_PREFIX = "#"; //$NON-NLS-1$ private static final String COLON = ":"; //$NON-NLS-1$ private static final String HTML_MIME_TYPE = "text/html"; //$NON-NLS-1$ private IContextInformationValidator validator; boolean initalPopup = false; boolean forceActivated = false; // icons private static Image fIconField = UnifiedEditorsPlugin.getImage("icons/field_public.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 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 CSSCompletionProposalComparator contentAssistComparator; /** The current offset into the document */ private int offset; /** * INSIDE_RULE indicates we are between a { and a } Here we show the full list of CSS properties, or filtered by * what is already typed */ public static String INSIDE_RULE = "INSIDE_RULE"; //$NON-NLS-1$ /** OUTSIDE_RULE indicates we are between two rules (between a } and a { */ public static String OUTSIDE_RULE = "OUTSIDE_RULE"; //$NON-NLS-1$ /** ARG_ASSIST indicates we are between a : and a ; */ public static String ARG_ASSIST = "ARG_ASSIST"; //$NON-NLS-1$ /** * ERROR indicates we in a problem state, and should bail */ public static String ERROR = "ERROR"; //$NON-NLS-1$ private String AUTO_ADDED = Messages.CSSContentAssistProcessor_AutoAdded; // private IFileLanguageService languageService; /** The current location of the cursor. Once of the static values above */ private String currentLocation; /** * The current lexeme hash. Can be one of the following: "": we're typing a new property "propPrefix": We've typed * some part of a new property "propPrefix:" We've typed a whole new property, and are on to argument assist * "propPrefix:valPrefix" : We've typed a whole new property, and have typed some part of the value name */ private String lexemeHash = StringUtils.EMPTY; /** The "property" from above */ private String propertyPrefix; /** The "value" from above */ private String valuePrefix; private EditorFileContext context; private IMetadataEnvironment environment; /** * Provides code assist information for CSS. * * @param context */ public CSSContentAssistProcessor(EditorFileContext context) { this.context = context; environment = (IMetadataEnvironment) CSSLanguageEnvironment.getInstance().getRuntimeEnvironment(); contentAssistComparator = new CSSCompletionProposalComparator(); validator = new CSSContextInformationValidator(this); } /** * The characters that triggers completion proposals * * @return Returns the trigger characters for code completion. */ public char[] getCompletionProposalAutoActivationCharacters() { return new char[] { ':', '\t', '{', ';' }; } /** * 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 int[] getCompletionProposalSeparatorLexemes() { return new int[] { CSSTokenTypes.COLON, CSSTokenTypes.SEMICOLON, CSSTokenTypes.RCURLY, CSSTokenTypes.LCURLY }; } /** * @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) { this.offset = offset; if (position < 0) { return new ICompletionProposal[0]; } Lexeme currentLexeme = lexemeList.get(position); if (unifiedViewer != null && unifiedViewer.isHotkeyActivated()) { unifiedViewer.setHotkeyActivated(false); activationChar = DEFAULT_CHARACTER; } if (currentLexeme != null && currentLexeme.getLanguage().equals(HTML_MIME_TYPE) && position > 0) { currentLexeme = lexemeList.get(position - 1); } currentLocation = getLocation(offset, position, lexemeList, previousChar); if (currentLocation.equals(ERROR)) { return new ICompletionProposal[0]; } setPrefixes(currentLocation); /* * CSSCompletionProposal cp = new CSSCompletionProposal( "", offset, 0, offset, fIconField, "No completions * available", null, null, CSSCompletionProposalComparator.OBJECT_TYPE_PROPERTY); */ ICompletionProposal[] result = null; // new ICompletionProposal[] { cp }; // if we are outside a rule, return no code assist if (currentLocation.equals(OUTSIDE_RULE)) { if ((propertyPrefix != null && !propertyPrefix.equals(StringUtils.EMPTY)) && (previousChar == ' ' || previousChar == '\t')) { return new ICompletionProposal[0]; } result = getElementCompletionProposals(propertyPrefix, previousChar, currentLexeme); setSelection(propertyPrefix, result); return result; } // previous char seems to be a default character if I have typed some content to start activation, like 'b' if (currentLocation.equals(INSIDE_RULE) && ((activationChar != ' ' && activationChar != '\t') || previousChar == DEFAULT_CHARACTER)) { boolean addColon = addColon(currentLexeme, lexemeList); boolean colonPref = insertColon(getPreferenceStore()); result = getAllPropertiesCompletionProposals(propertyPrefix, currentLexeme, addColon && colonPref); setSelection(propertyPrefix.toLowerCase(), result); return result; } if (currentLocation.equals(ARG_ASSIST)) { result = getSpecificPropertyCompletionProposals(offset, propertyPrefix, valuePrefix, currentLexeme, lexemeList); setSelectionUnsorted(valuePrefix, result); if (result.length == 0) { // show argument assist instead. For the moment, this is just info if (unifiedViewer != null)// && unifiedViewer instanceof SourceViewer) { ((SourceViewer) unifiedViewer).doOperation(SourceViewer.CONTENTASSIST_CONTEXT_INFORMATION); return result; } } else { return result; } } // if(!fullName.equals(CSSOffsetMapper.INSIDE_RULE)) // prefix = fullName; // calculate all the completion proposals. Right now, we just provide a // listing // of all possible CSS elements any time we are inside a rule. // This can be more sophisticated later, as for HTML elements, we also // know which elements can have which styles applied. // ICompletionProposal[] result = // getAllPropertiesCompletionProposals(prefix); return result; } /** * setPrefixes * * @param location */ public void setPrefixes(String location) { propertyPrefix = StringUtils.EMPTY; valuePrefix = StringUtils.EMPTY; // No semi-colon, so only a prop prefix if (!lexemeHash.equals(StringUtils.EMPTY)) { if (location.equals(OUTSIDE_RULE)) { if (lexemeHash.indexOf(COLON) < 0) { propertyPrefix = lexemeHash; } else { String[] lexemes = lexemeHash.split(COLON); propertyPrefix = lexemes[lexemes.length - 1]; } } else { if (lexemeHash.indexOf(COLON) < 0) { propertyPrefix = lexemeHash; } else { // In this case, we have both items in hash, so we give the one // before // the colon to the propertyName, and the one afterwards to the // value; String[] lexemes = lexemeHash.split(COLON); if (lexemes.length > 0) { propertyPrefix = lexemes[0]; if (lexemes.length > 1) { valuePrefix = lexemes[lexemes.length - 1]; } } } } } } /** * 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 CSSOffsetMapper * eventually) * * @param offset * The current offset * @param currentLexemePosition * @param ll * @param activationCharacter * @return One of the location enumerations */ public String getLocation(int offset, int currentLexemePosition, LexemeList ll, char activationCharacter) { if (offset == 0) { return OUTSIDE_RULE; } if (currentLexemePosition < 0) { return ERROR; } String location = OUTSIDE_RULE; int position = currentLexemePosition; ArrayList<String> currentHash = new ArrayList<String>(); // backtrack over lexemes to find name - we are really just // searching for the last OPEN_ELEMENT while (position >= 0) { Lexeme curLexeme = ll.get(position); if (!curLexeme.getLanguage().equals(CSSMimeType.MimeType)) { // for STU-1241 if (curLexeme.getLanguage().equals(HTML_MIME_TYPE)) { // Only iterate over range of css tokens! int start = 0; for (int i = position - 1; i >= 0; i--) { Lexeme lex = ll.get(i); if (lex.getLanguage().equals(HTML_MIME_TYPE)) { start = i + 1; break; } } if (position == start) { return INSIDE_RULE; } int size = position - start; String recursiveLocation = getLocation(offset, size - 1, new LexemeList(ll.copyRange(start, position - 1)), activationCharacter); this.offset = offset - lexemeHash.length(); if (recursiveLocation.equals(OUTSIDE_RULE) || recursiveLocation.equals(ERROR)) return INSIDE_RULE; return recursiveLocation; } break; } if (curLexeme.getText().startsWith(CSS_ID_PREFIX) || curLexeme.getText().startsWith(CSS_CLASS_PREFIX)) { if (curLexeme.getCategoryIndex() == TokenCategories.LITERAL) { currentHash.add(0, curLexeme.getText()); } else if (curLexeme.getCategoryIndex() == TokenCategories.ERROR && curLexeme.getText().length() == 1) { currentHash.add(0, curLexeme.getText()); } else if (curLexeme.getCategoryIndex() == TokenCategories.ERROR) { location = ERROR; break; } } if (curLexeme.getCategoryIndex() == TokenCategories.IDENTIFIER) { currentHash.add(0, curLexeme.getText()); } if (curLexeme.getCategoryIndex() == TokenCategories.LITERAL && curLexeme.typeIndex == CSSTokenTypes.STRING) { currentHash.add(0, curLexeme.getText()); } if (curLexeme.getCategoryIndex() == TokenCategories.KEYWORD && curLexeme.typeIndex == CSSTokenTypes.URL) { currentHash.add(0, curLexeme.getText()); } // If the current lexeme starts with a " as part of a value, it may not have an ending // ". This will result in an error if (curLexeme.getCategoryIndex() == TokenCategories.ERROR && curLexeme.getText().startsWith("\"")) //$NON-NLS-1$ { currentHash.add(0, curLexeme.getText()); } if (curLexeme.typeIndex == CSSTokenTypes.RCURLY) { location = OUTSIDE_RULE; break; } if (curLexeme.typeIndex == CSSTokenTypes.COLON) { location = ARG_ASSIST; // Watch it! I want to backtrack from where I am now, not where I was // so pass in curLexeme.startingOffset, not offset Lexeme prevIdentifier = getPreviousLexemeOfType(curLexeme.getStartingOffset(), new int[] { CSSTokenTypes.IDENTIFIER, CSSTokenTypes.SELECTOR, CSSTokenTypes.PROPERTY }, ll, false); if (prevIdentifier != null) { currentHash.add(0, prevIdentifier.getText()); } break; } if (curLexeme.typeIndex == CSSTokenTypes.SEMICOLON) { // same issue here as with colon Lexeme prevIdentifier = getPreviousLexemeOfType(curLexeme.getStartingOffset(), new int[] { CSSTokenTypes.LCURLY }, new int[] { CSSTokenTypes.RCURLY }, ll, false); if (prevIdentifier != null) { location = INSIDE_RULE; } else { location = ERROR; } break; } if (curLexeme.typeIndex == CSSTokenTypes.LCURLY) { location = INSIDE_RULE; break; } if (curLexeme.typeIndex == CSSTokenTypes.COMMA && location.equals(OUTSIDE_RULE)) { Lexeme prevCurly = getPreviousLexemeOfType(curLexeme.getStartingOffset(), new int[] { CSSTokenTypes.LCURLY }, new int[] { CSSTokenTypes.RCURLY }, ll, false); if (prevCurly != null) { location = INSIDE_RULE; } else { break; } } position--; } if (currentHash.size() > 0) { lexemeHash = StringUtils.join(COLON, currentHash.toArray(new String[currentHash.size()])); // We add a trailing ":" as that indicates we've finished typing // propertyName if (location.equals(ARG_ASSIST) && lexemeHash.indexOf(COLON) < 0) { lexemeHash += COLON; } } else { lexemeHash = StringUtils.EMPTY; } return location; } /** * @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#getContextInformationValidator() */ public IContextInformationValidator getContextInformationValidator() { return validator; } /** * @see com.aptana.ide.editors.unified.contentassist.IUnifiedContentAssistProcessor#getCompletionProposalIdleActivationTokens() */ public int[] getCompletionProposalIdleActivationTokens() { return new int[] { CSSTokenTypes.CLASS, CSSTokenTypes.IDENTIFIER, CSSTokenTypes.HASH, CSSTokenTypes.SELECTOR, CSSTokenTypes.PROPERTY }; } /** * @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#getErrorMessage() */ public String getErrorMessage() { return null; } /** * 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 currentLexeme * @return Returns an array of completion proposals. */ public ICompletionProposal[] getElementCompletionProposals(String prefix, char activationChar, Lexeme currentLexeme) { Hashtable<String, CSSCompletionProposal> completionProposals = new Hashtable<String, CSSCompletionProposal>(); // We need to test against lower-case items String testPrefix = prefix.toLowerCase(); int beginOffset = getOffsetForInsertion(offset, currentLexeme); String[] em = environment.getAllElements(); int replaceLength = testPrefix.length(); if (!prefix.startsWith(CSS_CLASS_PREFIX) && !prefix.startsWith(CSS_ID_PREFIX)) { for (int i = 0; i < em.length; i++) { String e = em[i]; String docText = environment.getElementDocumentation(e); String displayString = e; String replaceString = e; int cursorPosition = replaceString.length(); Image[] userAgents = null; userAgents = getUserAgentImages(getUserAgents(), environment.getUserAgentPlatformNames(e)); CSSCompletionProposal cp = new CSSCompletionProposal(replaceString, beginOffset, replaceLength, cursorPosition, fIconTag, displayString, null, docText, CSSCompletionProposalComparator.OBJECT_TYPE_PROPERTY, unifiedViewer, userAgents); completionProposals.put(displayString, cp); } } String path = getEditorContentsPath(); Collection<String> ids = CSSLanguageEnvironment.getInstance().getIds(path, ""); Collection<String> classes = CSSLanguageEnvironment.getInstance().getClasses(path, ""); if (ids != null) { for (String e : ids) { if (StringUtils.EMPTY.equals(e)) { continue; } String trimmedValue = CSS_ID_PREFIX + StringUtils.trimStringQuotes(e); String docText = StringUtils.format(Messages.CSSContentAssistProcessor_IDSelectorDescription, new String[] { trimmedValue, e, AUTO_ADDED }); String displayString = trimmedValue; String replaceString = trimmedValue; int cursorPosition = replaceString.length(); CSSCompletionProposal cp = new CSSCompletionProposal(replaceString, beginOffset, replaceLength, cursorPosition, fIconTagGuess, displayString, null, docText, CSSCompletionProposalComparator.OBJECT_TYPE_PROPERTY, unifiedViewer, getAllUserAgentImages(getUserAgents())); completionProposals.put(displayString, cp); } } if ((classes == null || classes.size() == 0) && prefix.startsWith(CSS_CLASS_PREFIX)) { return new ICompletionProposal[0]; } if (classes != null) { for (String e : classes) { String trimmedValue = CSS_CLASS_PREFIX + StringUtils.trimStringQuotes(e); String docText = StringUtils.format(Messages.CSSContentAssistProcessor_ClassSelectorDescription, new String[] { trimmedValue, e, AUTO_ADDED }); String displayString = trimmedValue; String replaceString = trimmedValue; int cursorPosition = replaceString.length(); CSSCompletionProposal cp = new CSSCompletionProposal(replaceString, beginOffset, replaceLength, cursorPosition, fIconTagGuess, displayString, null, docText, CSSCompletionProposalComparator.OBJECT_TYPE_PROPERTY, unifiedViewer, getAllUserAgentImages(getUserAgents())); completionProposals.put(displayString, cp); } } ICompletionProposal[] result = completionProposals.values() .toArray(new ICompletionProposal[completionProposals.size()]); Arrays.sort(result, contentAssistComparator); return result; } private String getEditorContentsPath() { IEditorInput pathEditor = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage() .getActiveEditor().getEditorInput(); if (pathEditor instanceof IFileEditorInput) { IFileEditorInput fileEI = (IFileEditorInput) pathEditor; return fileEI.getFile().getProject().getFullPath().toPortableString(); } return null; } /** * Returns a set of valid completion proposals. * * @param offset * @param propertyName * The text already typed for the "property" * @param valueName * The text already typed for the "value" * @param currentLexeme * @return Returns an array of completion proposals. */ public ICompletionProposal[] getSpecificPropertyCompletionProposals(int offset, String propertyName, String valueName, Lexeme currentLexeme, LexemeList lexemeList) { if (valueName.startsWith("url(")) { return getFilePathCompletionProposals(valueName, offset, 0); } else if (currentLexeme.getText().startsWith("url(")) { return getFilePathCompletionProposals(currentLexeme.getText(), offset, 0); } Lexeme lexeme = currentLexeme; String valueString = valueName; ArrayList<CSSCompletionProposal> completionProposals = new ArrayList<CSSCompletionProposal>(); ArrayList<ValueMetadata> addedFields = new ArrayList<ValueMetadata>(); // Global fields are the list of "all" fields Hashtable fields = environment.getGlobalFields(); String propertyNameLower = propertyName.toLowerCase(); // String valueNameLower = valueName.toLowerCase(); FieldMetadata fm = (FieldMetadata) fields.get(propertyNameLower); if (fm == null) { return new ICompletionProposal[0]; } Map<String, Image> colors = new HashMap<String, Image>(); boolean isColor = fm.getName().equals("background-color") || fm.getName().equals("color"); //$NON-NLS-1$ //$NON-NLS-2$ // Suggest colors already used in file! if (isColor) { fm.getValues().clear(); for (Image image : colors.values()) { image.dispose(); } colors.clear(); Collection<String> colorsUsedInProject = getColorsUsed(); // Color! Grab all colors defined in file and stick them at top for (String color : colorsUsedInProject) { if (!colors.containsKey(color)) { ValueMetadata value = new ValueMetadata(); value.setName(color); fm.addValue(value); colors.put(color, CSSColors.toImage(color, 16, 16)); } } } if (offset > lexeme.getEndingOffset() && fm.getAllowMultipleValues()) { lexeme = null; valueString = StringUtils.EMPTY; } int beginOffset = getOffsetForInsertion(offset, lexeme); boolean insertSemicolonPref = insertSemicolon(getPreferenceStore()); for (int i = 0; i < fm.getValues().size(); i++) { ValueMetadata value = (ValueMetadata) fm.getValues().get(i); String docText = StringUtils.EMPTY; 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(VALUE_PROPOSAL_ALL)) { replaceString = StringUtils.EMPTY; } else if (!fm.getAllowMultipleValues() && insertSemicolonPref) { replaceString = addSemicolonIfNecessary(replaceString, lexemeList, currentLexeme); } int cursorPosition = replaceString.length(); // TODO: Later, we may wish to replace _all_ values after the // colon (up to the next semi-colon) with // the added value int replaceLength = valueString.length(); Image[] userAgents = null; if (value.getUserAgents().length == 0) { userAgents = getUserAgentImages(getUserAgents(), fm.getUserAgentPlatformNames()); } else { userAgents = getUserAgentImages(getUserAgents(), value.getUserAgentPlatformNames()); } Image icon = fIconField; if (isColor) { icon = colors.get(value.getName()); } CSSCompletionProposal cp = new CSSCompletionProposal(replaceString, beginOffset, replaceLength, cursorPosition, icon, displayString, null, docText, CSSCompletionProposalComparator.OBJECT_TYPE_PROPERTY, unifiedViewer, userAgents); if (cp != null && !addedFields.contains(value)) { addedFields.add(value); completionProposals.add(cp); } } ICompletionProposal[] result = completionProposals .toArray(new ICompletionProposal[completionProposals.size()]); // Arrays.sort(result, contentAssistComparator); return result; } private Collection<String> getColorsUsed() { return CSSLanguageEnvironment.getInstance().getColors(getEditorContentsPath(), ""); } /** * Appends semicolon to replacement string for proposal if next lexeme is not a semicolon. * * @param replaceString * @param lexemeList * @param currentLexeme * @return */ private String addSemicolonIfNecessary(String replaceString, LexemeList lexemeList, Lexeme currentLexeme) { int currentPosition = lexemeList.getLexemeIndex(currentLexeme); if (lexemeList.size() <= currentPosition + 1) return replaceString + ";"; //$NON-NLS-1$; Lexeme next = lexemeList.get(currentPosition + 1); if (next != null && next.typeIndex == CSSTokenTypes.SEMICOLON) return replaceString; return replaceString + ";"; //$NON-NLS-1$ } /** * Gets the offset for inserting a new item into the document * * @param offset * @param currentLexeme * @return The index at which to insert */ public int getOffsetForInsertion(int offset, Lexeme currentLexeme) { if (currentLexeme == null) { return offset; } int beginOffset = offset; // have to check that the current lexeme is a CSS lexeme // TODO: Move this further up the chain. if (!currentLexeme.getLanguage().equals(CSSMimeType.MimeType)) { return offset; } // If we're in an identifier, it's likely we'll want to replace that // current value if (currentLexeme.typeIndex == CSSTokenTypes.IDENTIFIER || currentLexeme.typeIndex == CSSTokenTypes.PROPERTY || currentLexeme.typeIndex == CSSTokenTypes.SELECTOR || currentLexeme.getCategoryIndex() == TokenCategories.LITERAL // here, we assume if we're in an error, we just replace the while thing || (currentLexeme.getCategoryIndex() == TokenCategories.ERROR)) { beginOffset = currentLexeme.getStartingOffset(); } return beginOffset; } /** * addColon * * @param currentLexeme * @param lexemeList * @return boolean */ public boolean addColon(Lexeme currentLexeme, LexemeList lexemeList) { if (currentLexeme == null) { return false; } int lexemeIndex = lexemeList.getLexemeIndex(currentLexeme); if (lexemeIndex < lexemeList.size() - 1) { Lexeme sibling = lexemeList.get(lexemeIndex + 1); if (sibling != null && sibling.typeIndex != CSSTokenTypes.COLON) { return true; } } return false; } /** * Returns a set of valid completion proposals. * * @param prefix * The text already typed (to filter on) * @param currentLexeme * @param addColon * @return Returns an array of completion proposals. */ public ICompletionProposal[] getAllPropertiesCompletionProposals(String prefix, Lexeme currentLexeme, boolean addColon) { Hashtable<String, CSSCompletionProposal> completionProposals = new Hashtable<String, CSSCompletionProposal>(); // Global fields are the list of "all" fields Hashtable fields = environment.getGlobalFields(); Collection vals = fields.values(); Iterator iter = vals.iterator(); // We need to test against lower-case items String testPrefix = prefix.toLowerCase(); int beginOffset = getOffsetForInsertion(offset, currentLexeme); while (iter.hasNext()) { FieldMetadata fm = (FieldMetadata) iter.next(); String docText = StringUtils.EMPTY; docText = environment.getFieldDocumentation(fm); // TODO Proper-case string back to what they originally had // Commented out the ":" as it was giving grief with auto-pop; String replaceString = fm.getName(); // [IM] Don't add on ':' for the moment if (addColon) { replaceString += COLON; } String displayString = fm.getName(); int cursorPosition = replaceString.length(); // TODO: Later, we may wish to replace _all_ values after the // colon (up to the next semi-colon) with // the added value int replaceLength = testPrefix.length(); Image[] userAgents = getUserAgentImages(getUserAgents(), fm.getUserAgentPlatformNames()); CSSCompletionProposal cp = new CSSCompletionProposal(replaceString, beginOffset, replaceLength, cursorPosition, fIconField, displayString, null, docText, CSSCompletionProposalComparator.OBJECT_TYPE_PROPERTY, unifiedViewer, userAgents); if (cp != null) { completionProposals.put(fm.getName(), cp); // cp.scheduleContentAssistOnInsert(true); } } ICompletionProposal[] result = completionProposals.values() .toArray(new ICompletionProposal[completionProposals.size()]); Arrays.sort(result, contentAssistComparator); return result; } /** * getOffsetMapper * * @return CSSOffsetMapper */ public CSSOffsetMapper getCSSOffsetMapper() { return (CSSOffsetMapper) getOffsetMapper(); } /** * Returns a reference to the current offsetMapper * * @return The reference to the mapper */ public IOffsetMapper getOffsetMapper() { IFileLanguageService ls = this.context.getLanguageService(CSSMimeType.MimeType); if (ls != null) { return ls.getOffsetMapper(); } else { return null; } } /** * Calculates the current lexeme in the document * * @param offset * the offset of the current insertion point * @return The current lexeme, or null if not found */ public Lexeme getCurrentLexeme(int offset) { this.getOffsetMapper().calculateCurrentLexeme(offset); Lexeme currentLexeme = this.getOffsetMapper().getCurrentLexeme(); return currentLexeme; } /** * @see UnifiedContentAssistProcessor#getPreferenceStore() */ protected IPreferenceStore getPreferenceStore() { if (PluginUtils.isPluginLoaded(CSSPlugin.getDefault())) { return CSSPlugin.getDefault().getPreferenceStore(); } else { return null; } } /** * getPropertyPrefix * * @return String */ public String getPropertyPrefix() { return propertyPrefix; } /** * getValuePrefix * * @return String */ public String getValuePrefix() { return valuePrefix; } /** * @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; if (propertyPrefix == null) { return new IContextInformation[0]; } Hashtable<String, FieldMetadata> fields = environment.getGlobalFields(); String propertyNameLower = propertyPrefix.toLowerCase(); // Don't show context information until I can eliminate the double // popups. // if (true) // return null; FieldMetadata fm = (FieldMetadata) fields.get(propertyNameLower); if (fm == null) { return new IContextInformation[0]; } // Right now we just print the description. This should be a list of // arguments, like VS 2003 String hint = fm.getDescription(); if (fm.getHint() != null) { hint = fm.getHint(); } if (hint == null) { hint = Messages.CSSContentAssistProcessor_NoHintAvailable; } ContextInformation ci = new ContextInformation("contextDisplayString", StringUtils //$NON-NLS-1$ .format(Messages.CSSContentAssistProcessor_ContextHint, new String[] { fm.getName(), hint })); if (ci != null) { ici = new IContextInformation[] { ci }; } return ici; } /** * Do we insert a colon? * * @param store * @return String */ public static boolean insertColon(IPreferenceStore store) { if (store != null) { return store.getBoolean(IPreferenceConstants.CSSEDITOR_INSERT_COLON); } else { return false; } } /** * Do we insert a semicolon? * * @param store * @return String */ public static boolean insertSemicolon(IPreferenceStore store) { if (store != null) { return store.getBoolean(IPreferenceConstants.CSSEDITOR_INSERT_SEMICOLON); } else { return false; } } /** * @see UnifiedContentAssistProcessor#getProposalComparator() */ public Comparator<ICompletionProposal> getProposalComparator() { return contentAssistComparator; } /** * getFilePathCompletionProposals * * @param valuePrefix * @param beginOffset * @param replaceLength * @param sortingType */ private ICompletionProposal[] getFilePathCompletionProposals(String valuePrefix, int beginOffset, int replaceLength) { List<UnifiedCompletionProposal> completionProposals = new ArrayList<UnifiedCompletionProposal>(); IEditorInput pathEditor = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage() .getActiveEditor().getEditorInput(); String urlPrefix = null; if (pathEditor instanceof IFileEditorInput) { urlPrefix = getProjectContextRoot((IFileEditorInput) pathEditor); } String editorPath = CoreUIUtils.getPathFromEditorInput(pathEditor); String basePath = new Path(CoreUIUtils.getPathFromEditorInput(pathEditor)).removeLastSegments(1) .toOSString(); if (urlPrefix != null && !"".equals(urlPrefix)) //$NON-NLS-1$ { basePath = urlPrefix; } String currentPath = basePath; if (valuePrefix != null) { if (valuePrefix.startsWith("url(")) { valuePrefix = valuePrefix.substring(4); } if (valuePrefix.endsWith(")")) { valuePrefix = valuePrefix.substring(0, valuePrefix.length() - 1); } String s = StringUtils.trimStringQuotes(valuePrefix); if (!"".equals(s)) //$NON-NLS-1$ { beginOffset -= s.length(); replaceLength += s.length(); File current = new File(currentPath); if (current.isDirectory()) { if (currentPath.endsWith(File.separator)) currentPath = currentPath.substring(0, currentPath.length() - 1); 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 (File f : files) { if (f.getName().startsWith(".")) //$NON-NLS-1$ { continue; } // Don't include the current file in the list if (f.toString().equals(editorPath)) { continue; } Image image = getImage(f); String replaceString = FileUtils.makeFilePathRelative(new File(basePath), f); replaceString = replaceString.replaceAll("\\\\", "/"); //$NON-NLS-1$ //$NON-NLS-2$ String displayString = CoreUIUtils.getPathFromURI(replaceString); int cursorPosition = replaceString.length(); CSSCompletionProposal cp = new CSSCompletionProposal(replaceString, beginOffset, replaceLength, cursorPosition, image, displayString, null, f.toString(), CSSCompletionProposalComparator.OBJECT_TYPE_PROPERTY, unifiedViewer, new Image[0]); if (cp != null) { completionProposals.add(cp); } } return completionProposals.toArray(new ICompletionProposal[0]); } private Image getImage(File f) { String fileType = ""; //$NON-NLS-1$ try { fileType = FileSystemView.getFileSystemView().getSystemTypeDescription(f); } catch (Exception ex) { IdeLog.logError(CSSPlugin.getDefault(), "FIXME!!!!", ex); } Image image = null; if (fileType != null) { image = com.aptana.ide.core.ui.ImageUtils.fileIconsHash.get(fileType); } if (image == null) { image = fIconFile; if (f.isDirectory()) { image = fIconFolder; } } return image; } /** * Get the name of the project context root * * @param input * @return * @throws CoreException */ private String getProjectContextRoot(IFileEditorInput input) { String urlPrefix = "/"; IFile file = input.getFile(); IProject project = file.getProject(); try { String contextRoot = project.getPersistentProperty(new QualifiedName("", //$NON-NLS-1$ "com.aptana.ide.editor.html.preview.CONTEXT_ROOT")); if (contextRoot != null && !contextRoot.equals("/")) //$NON-NLS-1$ { urlPrefix = contextRoot + "/"; } } catch (CoreException e) { // TODO Auto-generated catch block e.printStackTrace(); } return project.getLocation().append(urlPrefix).toOSString(); } }