| /******************************************************************************* |
| * Copyright (c) 2004, 2010 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.wst.css.ui.internal.contentassist; |
| |
| |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.eclipse.wst.css.core.internal.CSSCorePlugin; |
| import org.eclipse.wst.css.core.internal.metamodel.CSSMMDescriptor; |
| import org.eclipse.wst.css.core.internal.metamodel.CSSMMFunction; |
| import org.eclipse.wst.css.core.internal.metamodel.CSSMMNode; |
| import org.eclipse.wst.css.core.internal.metamodel.CSSMMNumber; |
| import org.eclipse.wst.css.core.internal.metamodel.CSSMMProperty; |
| import org.eclipse.wst.css.core.internal.metamodel.CSSMMUnit; |
| import org.eclipse.wst.css.core.internal.metamodel.util.CSSFunctionID; |
| import org.eclipse.wst.css.core.internal.metamodel.util.CSSMetaModelUtil; |
| import org.eclipse.wst.css.core.internal.parserz.CSSRegionContexts; |
| import org.eclipse.wst.css.core.internal.preferences.CSSCorePreferenceNames; |
| import org.eclipse.wst.css.core.internal.provisional.document.ICSSNode; |
| import org.eclipse.wst.css.core.internal.provisional.document.ICSSPrimitiveValue; |
| import org.eclipse.wst.css.core.internal.provisional.document.ICSSStyleDeclItem; |
| import org.eclipse.wst.css.core.internal.util.CSSUtil; |
| import org.eclipse.wst.css.core.internal.util.RegionIterator; |
| import org.eclipse.wst.css.ui.internal.image.CSSImageType; |
| import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; |
| import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion; |
| import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion; |
| import org.w3c.dom.css.CSSFontFaceRule; |
| |
| class CSSProposalGeneratorForDeclarationValue extends CSSProposalGenerator { |
| |
| private static final String IMPORTANT = "!important"; //$NON-NLS-1$ |
| private boolean fUseUpperCase; |
| private boolean fAppendSemiColon; |
| |
| /** |
| * CSSProposalGeneratorForDeclarationValue constructor comment. |
| * |
| */ |
| CSSProposalGeneratorForDeclarationValue(CSSContentAssistContext context) { |
| super(context); |
| fUseUpperCase = CSSCorePlugin.getDefault().getPluginPreferences().getInt(CSSCorePreferenceNames.CASE_PROPERTY_VALUE) == CSSCorePreferenceNames.UPPER; |
| } |
| |
| /** |
| * |
| */ |
| private void addFunction(List candidates, CSSMMFunction prop) { |
| String text = prop.toString(); |
| if (!isMatch(text)) { |
| return; |
| } |
| |
| int cursorPos = 0; |
| StringBuffer buf = new StringBuffer(); |
| if (prop.getName().equals(CSSFunctionID.F_URI)) { |
| StringAndOffset sao = generateURI(); |
| buf.append(sao.fString); |
| cursorPos = sao.fOffset; |
| } else { |
| buf.append(prop.toString()); |
| cursorPos = buf.length(); |
| StringAndOffset sao = generateParenthesis(); |
| buf.append(sao.fString); |
| cursorPos += sao.fOffset; |
| } |
| |
| text = buf.toString(); |
| text = (fUseUpperCase) ? text.toUpperCase() : text.toLowerCase(); |
| |
| CSSCACandidate item = new CSSCACandidate(); |
| item.setReplacementString(text); |
| item.setCursorPosition(cursorPos); |
| item.setDisplayString(text); |
| item.setMMNode(prop); |
| item.setImageType(CSSImageType.VALUE_FUNCTION); |
| appendSemiColon(item); |
| candidates.add(item); |
| } |
| |
| /** |
| * |
| */ |
| private void addNumber(List candidates, CSSMMNumber prop) { |
| String fullText = fContext.getTextToReplace(); |
| // skip number |
| int unitIndex = -1; |
| for (int i = 0; i < fullText.length(); i++) { |
| if (Character.isDigit(fullText.charAt(i))) { |
| unitIndex = i + 1; |
| } else { |
| break; |
| } |
| } |
| |
| String unitSubText = ""; //$NON-NLS-1$ |
| String numSubText = ""; //$NON-NLS-1$ |
| if (0 <= unitIndex) { |
| numSubText = fullText.substring(0, unitIndex); |
| if (unitIndex < fullText.length()) { |
| unitSubText = fullText.substring(unitIndex); |
| } |
| } else { |
| unitSubText = fullText; |
| } |
| |
| Iterator i = prop.getDescendants(); |
| while (i.hasNext()) { |
| CSSMMUnit unit = (CSSMMUnit) i.next(); |
| String unitString = unit.getUnitString(); |
| if ((0 < unitSubText.length() && unitString.indexOf(unitSubText) != 0) || (0 < numSubText.length() && unitString.equals("#"))) { //$NON-NLS-1$ |
| continue; |
| } |
| |
| String text = numSubText + unitString; |
| text = (fUseUpperCase) ? text.toUpperCase() : text.toLowerCase(); |
| CSSCACandidate item = new CSSCACandidate(); |
| item.setReplacementString(text); |
| if (0 < numSubText.length() || text.equals("#")) { //$NON-NLS-1$ |
| item.setCursorPosition(text.length()); |
| } else { |
| item.setCursorPosition(0); |
| } |
| item.setDisplayString(text); |
| item.setImageType(CSSImageType.VALUE_NUMBER); |
| appendSemiColon(item); |
| candidates.add(item); |
| } |
| } |
| |
| /** |
| * |
| */ |
| private void checkSemiColon() { |
| fAppendSemiColon = false; |
| |
| ITextRegion targetRegion = fContext.getTargetRegion(); |
| if (targetRegion != null && targetRegion.getType() != CSSRegionContexts.CSS_DECLARATION_DELIMITER) { |
| // find trailing ":" or ";" |
| // if ":" before ";" is found, add ";" |
| RegionIterator iterator = fContext.getRegionIterator(); |
| IStructuredDocumentRegion container = iterator.getStructuredDocumentRegion(); |
| while (iterator.hasNext()) { |
| ITextRegion region = iterator.next(); |
| if (iterator.getStructuredDocumentRegion() != container) { |
| break; |
| } |
| if (region.getType() == CSSRegionContexts.CSS_DECLARATION_SEPARATOR) { |
| fAppendSemiColon = true; |
| break; |
| } |
| } |
| if (!fAppendSemiColon) { |
| // second chance: |
| // leading IStructuredDocumentRegion is not ";" |
| IStructuredDocumentRegion nextStructuredDocumentRegion = CSSUtil.findNextSignificantNode(container); |
| if (CSSUtil.getStructuredDocumentRegionType(nextStructuredDocumentRegion) != CSSRegionContexts.CSS_DECLARATION_DELIMITER) { |
| fAppendSemiColon = true; |
| } |
| } |
| } |
| } |
| |
| /** |
| * |
| */ |
| private void appendSemiColon(CSSCACandidate item) { |
| if (fAppendSemiColon) { |
| String replacementString = item.getReplacementString(); |
| item.setReplacementString(replacementString + ";"); //$NON-NLS-1$ |
| int cursorPosition = item.getCursorPosition(); |
| if (replacementString.length() <= cursorPosition) { |
| // cursorpos is tail of string |
| cursorPosition++; |
| item.setCursorPosition(cursorPosition); |
| } |
| } |
| } |
| |
| /** |
| * |
| */ |
| private void addSemiColon(List candidates) { |
| ICSSNode targetNode = fContext.getTargetNode(); |
| if (targetNode instanceof ICSSStyleDeclItem) { |
| ICSSNode firstChild = targetNode.getFirstChild(); |
| if (firstChild == null) { |
| return; |
| } |
| if (firstChild instanceof IndexedRegion) { |
| int startOffset = ((IndexedRegion) firstChild).getStartOffset(); |
| if (fContext.getCursorPos() <= startOffset) { |
| return; |
| } |
| } |
| } |
| |
| boolean bAddCloser = false; |
| |
| ITextRegion targetRegion = fContext.getTargetRegion(); |
| if (targetRegion != null && targetRegion.getType() != CSSRegionContexts.CSS_DECLARATION_DELIMITER) { |
| // find trailing ":" or ";" |
| // if ":" before ";" is found, add ";" |
| RegionIterator iterator = fContext.getRegionIterator(); |
| IStructuredDocumentRegion container = iterator.getStructuredDocumentRegion(); |
| while (iterator.hasNext()) { |
| ITextRegion region = iterator.next(); |
| if (iterator.getStructuredDocumentRegion() != container) { |
| break; |
| } |
| if (region.getType() == CSSRegionContexts.CSS_DECLARATION_SEPARATOR) { |
| bAddCloser = true; |
| break; |
| } |
| } |
| if (!bAddCloser) { |
| // second chance: |
| // leading IStructuredDocumentRegion is not ";" |
| IStructuredDocumentRegion nextStructuredDocumentRegion = CSSUtil.findNextSignificantNode(container); |
| if (CSSUtil.getStructuredDocumentRegionType(nextStructuredDocumentRegion) != CSSRegionContexts.CSS_DECLARATION_DELIMITER) { |
| bAddCloser = true; |
| } |
| } |
| } |
| |
| if (bAddCloser) { |
| CSSCACandidate item = new CSSCACandidate(); |
| String text = fContext.getTextToReplace() + ";";//$NON-NLS-1$ |
| item.setReplacementString(text); |
| item.setCursorPosition(text.length()); |
| item.setDisplayString(";");//$NON-NLS-1$ |
| item.setImageType(null); |
| candidates.add(item); |
| } |
| } |
| |
| /** |
| * |
| */ |
| private void addString(List candidates, String text) { |
| if (!isMatch(text)) { |
| return; |
| } |
| |
| text = (fUseUpperCase) ? text.toUpperCase() : text.toLowerCase(); |
| |
| CSSCACandidate item = new CSSCACandidate(); |
| item.setReplacementString(text); |
| item.setCursorPosition(text.length()); |
| item.setDisplayString(text); |
| item.setImageType(CSSImageType.VALUE_STRING); |
| appendSemiColon(item); |
| candidates.add(item); |
| } |
| |
| private void addImportant(List candidates) { |
| ICSSNode targetNode = fContext.getTargetNode(); |
| while (targetNode instanceof ICSSPrimitiveValue) { |
| targetNode = targetNode.getParentNode(); |
| } |
| if (!(targetNode instanceof ICSSStyleDeclItem)) { |
| return; |
| } |
| // 1. has no priority region |
| // 2. cursor position is after of last child |
| // 3. normal isMatch method |
| String priority = ((ICSSStyleDeclItem) targetNode).getPriority(); |
| if (priority == null || priority.length() == 0) { |
| ICSSNode lastChild = targetNode.getLastChild(); |
| if (lastChild instanceof IndexedRegion) { |
| int startOffset = ((IndexedRegion) lastChild).getStartOffset(); |
| // int endOffset = ((IndexedRegion)lastChild).getEndOffset(); |
| if (startOffset < fContext.getCursorPos() && isMatch(IMPORTANT)) { |
| CSSCACandidate item = new CSSCACandidate(); |
| item.setReplacementString(IMPORTANT); |
| item.setCursorPosition(IMPORTANT.length()); |
| item.setDisplayString(IMPORTANT); |
| item.setImageType(CSSImageType.VALUE_STRING); |
| appendSemiColon(item); |
| candidates.add(item); |
| } |
| } |
| } |
| } |
| |
| /** |
| * getCandidates method comment. |
| */ |
| protected Iterator getCandidates() { |
| List candidates = new ArrayList(); |
| |
| checkSemiColon(); // check should add semi-colon or not |
| |
| String name = getPropertyName(); |
| if (name != null) { |
| CSSMetaModelUtil util = new CSSMetaModelUtil(fContext.getMetaModel()); |
| Iterator i = Collections.EMPTY_LIST.iterator(); |
| if (isFontFaceRule()) { |
| CSSMMDescriptor desc = util.getDescriptor(name); |
| if (desc != null) { |
| i = desc.getValues(); |
| } |
| } else { |
| CSSMMProperty prop = util.getProperty(name); |
| if (prop != null) { |
| i = prop.getValues(); |
| } |
| } |
| while (i.hasNext()) { |
| CSSMMNode val = (CSSMMNode) i.next(); |
| String valueType = val.getType(); |
| if (valueType == CSSMMNode.TYPE_KEYWORD) { |
| addString(candidates, val.toString()); |
| } else if (valueType == CSSMMNode.TYPE_NUMBER) { |
| addNumber(candidates, (CSSMMNumber) val); |
| } else if (valueType == CSSMMNode.TYPE_FUNCTION) { |
| addFunction(candidates, (CSSMMFunction) val); |
| } |
| } |
| } |
| |
| addImportant(candidates); |
| addSemiColon(candidates); |
| |
| return candidates.iterator(); |
| } |
| |
| /** |
| * @return java.lang.String |
| */ |
| private String getPropertyName() { |
| ICSSNode targetNode = fContext.getTargetNode(); |
| while (targetNode instanceof ICSSPrimitiveValue) { |
| targetNode = targetNode.getParentNode(); |
| } |
| if (targetNode instanceof ICSSStyleDeclItem) { |
| return ((ICSSStyleDeclItem) targetNode).getPropertyName(); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * |
| */ |
| private boolean isFontFaceRule() { |
| ICSSNode targetNode = fContext.getTargetNode(); |
| while (targetNode instanceof ICSSPrimitiveValue) { |
| targetNode = targetNode.getParentNode(); |
| } |
| if (targetNode instanceof ICSSStyleDeclItem) { |
| targetNode = targetNode.getParentNode(); // get Declaration |
| if (targetNode != null) { |
| // inline style has no rule node |
| targetNode = targetNode.getParentNode(); // get rule |
| } |
| } |
| return (targetNode instanceof CSSFontFaceRule); |
| } |
| } |