intial source from ttp://www.sf.net/projects/wdte
[phpeclipse.git] / archive / net.sourceforge.phpeclipse.css.ui / src / net / sourceforge / phpeclipse / css / ui / internal / text / CssContentAssistProcessor.java
diff --git a/archive/net.sourceforge.phpeclipse.css.ui/src/net/sourceforge/phpeclipse/css/ui/internal/text/CssContentAssistProcessor.java b/archive/net.sourceforge.phpeclipse.css.ui/src/net/sourceforge/phpeclipse/css/ui/internal/text/CssContentAssistProcessor.java
new file mode 100644 (file)
index 0000000..c3a712e
--- /dev/null
@@ -0,0 +1,408 @@
+/*
+ * Copyright (c) 2003-2004 Christopher Lenz and others.
+ * All rights reserved. This program and the accompanying materials 
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * Contributors:
+ *     Christopher Lenz - initial API and implementation
+ * 
+ * $Id: CssContentAssistProcessor.java,v 1.1 2004-09-02 18:11:48 jsurfer Exp $
+ */
+
+package net.sourceforge.phpeclipse.css.ui.internal.text;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
+import net.sourceforge.phpeclipse.core.model.ISourceReference;
+import net.sourceforge.phpeclipse.css.core.internal.text.CssTextUtils;
+import net.sourceforge.phpeclipse.css.core.model.IAtRule;
+import net.sourceforge.phpeclipse.css.core.model.IDeclaration;
+import net.sourceforge.phpeclipse.css.core.model.IPropertyInfo;
+import net.sourceforge.phpeclipse.css.core.model.IRule;
+import net.sourceforge.phpeclipse.css.core.model.IStyleRule;
+import net.sourceforge.phpeclipse.css.core.model.IStyleSheet;
+import net.sourceforge.phpeclipse.css.core.profiles.IProfile;
+import net.sourceforge.phpeclipse.css.ui.CssUI;
+import net.sourceforge.phpeclipse.css.ui.internal.CssDocumentProvider;
+import net.sourceforge.phpeclipse.css.ui.internal.CssUIPreferences;
+
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.text.contentassist.CompletionProposal;
+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.texteditor.IDocumentProvider;
+import org.eclipse.ui.texteditor.ITextEditor;
+
+/**
+ * Content assist processor for the CSS editor.
+ * 
+ * TODO If a completion proposal is requested before the style sheet has been
+ *      reconciled, we might not get any proposals, or they might be false.
+ */
+public class CssContentAssistProcessor implements IContentAssistProcessor {
+
+       // Constants ---------------------------------------------------------------
+
+       private static final String AUTOACTIVATION_TRIGGERS =
+               CssUIPreferences.CONTENTASSIST_AUTOACTIVATION_TRIGGERS;
+
+       private static final String ORDER_PROPOSALS =
+               CssUIPreferences.CONTENTASSIST_ORDER_PROPOSALS;
+
+       // Instance Variables ------------------------------------------------------
+
+       /**
+        * The preference store.
+        */
+       private IPreferenceStore store;
+
+       /**
+        * The current CSS profile.
+        */
+       private IProfile profile;
+
+       /**
+        * The associated text editor, if any.
+        */
+       private ITextEditor editor;
+
+       // Constructors ------------------------------------------------------------
+
+       /**
+        * Constructor.
+        * 
+        * @param profile The CSS profile to use
+        */
+       public CssContentAssistProcessor(IPreferenceStore store, IProfile profile,
+               ITextEditor editor) {
+               this.store = store;
+               this.profile = profile;
+               this.editor = editor;
+       }
+
+       // IContentAssistProcessor -------------------------------------------------
+
+       /*
+        * @see IContentAssistProcessor#computeCompletionProposals
+        */
+       public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer,
+               int documentOffset) {
+               List retVal = Collections.EMPTY_LIST;
+               IDocument document = viewer.getDocument();
+               try {
+                       String prefix = getPrefix(document, documentOffset);
+                       if (prefix.startsWith("@")) { //$NON-NLS-1$
+                               retVal = proposeAtKeywords(document, documentOffset,
+                                       prefix.substring(1));
+                       } else if (prefix.startsWith(":")) { //$NON-NLS-1$
+                               retVal = proposePseudoClasses(document, documentOffset,
+                                       prefix.substring(1));
+                       } else {
+                               retVal = proposeProperties(document, documentOffset, prefix);
+                       }
+                       if ((store != null) && store.getBoolean(ORDER_PROPOSALS)) {
+                               sortProposals(retVal);
+                       }
+               } catch (BadLocationException e) {
+                       CssUI.log(
+                               "Unable to compute completion proposals", e); //$NON-NLS-1$
+               }
+               return (ICompletionProposal[]) retVal.toArray(
+                       new ICompletionProposal[retVal.size()]);
+       }
+
+       /*
+        * @see IContentAssistProcessor#computeContextInformation
+        */
+       public IContextInformation[] computeContextInformation(ITextViewer viewer,
+               int documentOffset) {
+               return new IContextInformation[0];
+       }
+
+       /*
+        * @see IContentAssistProcessor#getCompletionProposalAutoActivationCharacters
+        */
+       public char[] getCompletionProposalAutoActivationCharacters() {
+               String chars = store.getString(AUTOACTIVATION_TRIGGERS);
+               if (chars == null) {
+                       return null;
+               }
+               return chars.toCharArray();
+       }
+
+       /*
+        * @see IContentAssistProcessor#getContextInformationAutoActivationCharacters
+        */
+       public char[] getContextInformationAutoActivationCharacters() {
+               return null;
+       }
+
+       /*
+        * @see IContentAssistProcessor#getErrorMessage
+        */
+       public String getErrorMessage() {
+               return null;
+       }
+
+       /*
+        * @see IContentAssistProcessor#getContextInformationValidator
+        */
+       public IContextInformationValidator getContextInformationValidator() {
+               return null;
+       }
+
+       // Private Methods ---------------------------------------------------------
+
+       /**
+        * Returns the part of the word immediately before the position at which
+        * content assist was requested. The prefix is lower-cased before it is 
+        * returned, to enable case-insensitive matching.
+        * 
+        * @param document the document
+        * @param offset the offset into the document
+        * @return the prefix
+        */
+       private String getPrefix(IDocument document, int offset) {
+               try {
+                       int startPos = offset;
+                       while (startPos > 0) {
+                               char c = document.getChar(startPos - 1);
+                               if (!CssTextUtils.isCssIdentifierPart(c)
+                                && (c != '@') && (c != ':')) {
+                                       break;
+                               }
+                               startPos--;
+                               if ((c == '@') || (c == ':')) {
+                                       break;
+                               }
+                       }
+                       if (startPos < offset) {
+                               return document.get(startPos, offset - startPos).toLowerCase();
+                       }
+               } catch (BadLocationException e) {
+                       e.printStackTrace();
+               }
+               return ""; //$NON-NLS-1$
+       }
+
+       /**
+        * Returns the parsed model of the document loaded in the editor, or
+        * <code>null</code> if the editor hasn't been set or the model couldn't be
+        * retrieved.
+        * 
+        * @return the parsed model
+        */
+       private IStyleSheet getStyleSheet() {
+               if (editor != null) {
+                       IDocumentProvider provider = editor.getDocumentProvider();
+                       if (provider instanceof CssDocumentProvider) {
+                               return ((CssDocumentProvider) provider).getStyleSheet(
+                                       editor.getEditorInput());
+                       }
+               }
+               return null;
+       }
+
+       public boolean isAtKeyword(IDocument document, int offset)
+               throws BadLocationException {
+               IStyleSheet styleSheet = getStyleSheet();
+               if (styleSheet != null) {
+                       IRule rule = styleSheet.getRuleAt(offset);
+                       if (rule instanceof IAtRule) {
+                               ISourceReference name = ((IAtRule) rule).getName();
+                               if (name != null) {
+                                       IRegion region = name.getSourceRegion();
+                                       for (int j = region.getOffset(); j < offset; j++) {
+                                               char ch = document.getChar(j);
+                                               if (!CssTextUtils.isCssIdentifierPart(ch)) {
+                                                       return false;
+                                               }
+                                       }
+                                       return true;
+                               }
+                       } else if (rule == null) {
+                               return true;
+                       }
+               }
+               return false;
+       }
+
+       /**
+        * Determines whether the document contains a property at the specified
+        * offset, or at least whether a property is theoretically allowed at that
+        * offset. 
+        * 
+        * @param document the document
+        * @param offset the offset into the document
+        * @return <code>true</code> if the specified offset is a legal position for
+        *         a property, <code>false</code> otherwise
+        * @throws BadLocationException if there was a problem accessing the
+        *         document
+        */
+       public boolean isProperty(IDocument document, int offset)
+               throws BadLocationException {
+               IStyleSheet styleSheet = getStyleSheet();
+               if (styleSheet != null) {
+                       IRule rule = styleSheet.getRuleAt(offset);
+                       if (rule != null) {
+                               IDeclaration declaration = rule.getDeclarationAt(offset);
+                               if (declaration != null) {
+                                       IRegion region = declaration.getSourceRegion();
+                                       for (int j = region.getOffset(); j < offset; j++) {
+                                               if (document.getChar(j) == ':') {
+                                                       return false;
+                                               }
+                                       }
+                                       return true;
+                               } else {
+                                       IRegion region = rule.getSourceRegion();
+                                       for (int j = region.getOffset(); j < offset; j++) {
+                                               if (document.getChar(j) == '{') {
+                                                       return true;
+                                               }
+                                       }
+                               }
+                       }
+               }
+               return false;
+       }
+
+       /**
+        * Determines whether the document contains a selector at the specified
+        * offset, or at least whether a selector is theoretically allowed at that
+        * offset.
+        */
+       public boolean isSelector(IDocument document, int offset)
+               throws BadLocationException {
+               IStyleSheet styleSheet = getStyleSheet();
+               if (styleSheet != null) {
+                       IRule rule = styleSheet.getRuleAt(offset);
+                       if (rule instanceof IStyleRule) {
+                               ISourceReference selector = ((IStyleRule) rule).getSelector();
+                               if (selector != null) {
+                                       IRegion region = selector.getSourceRegion();
+                                       for (int j = region.getOffset(); j < offset; j++) {
+                                               if (document.getChar(j) == '{') {
+                                                       return false;
+                                               }
+                                       }
+                                       return true;
+                               }
+                       } else if (rule == null) {
+                               return true;
+                       }
+               }
+               return false;
+       }
+
+       private List proposeAtKeywords(IDocument document, int offset,
+               String prefix) throws BadLocationException {
+               List proposals = new ArrayList();
+               if (isAtKeyword(document, offset)) {
+                       Image icon = CssUI.getDefault().getImageRegistry().get(
+                               CssUI.ICON_AT_RULE);
+                       Collection atRuleNames = profile.getAtKeywords();
+                       for (Iterator i = atRuleNames.iterator(); i.hasNext(); ) {
+                               String atRuleName = (String) i.next();
+                               if (atRuleName.startsWith(prefix)) {
+                                       ICompletionProposal proposal = new CompletionProposal(
+                                               atRuleName, offset - prefix.length(), prefix.length(),
+                                               atRuleName.length(), icon, atRuleName, null, null);
+                                       proposals.add(proposal);
+                               }
+                       }
+               }
+               return proposals;
+       }
+
+       /**
+        * Computes the completion proposals for properties. A property may only
+        * appear at the left-hand side of a declaration, so we check whether the
+        * document offset for which the proposals were requested is a legal
+        * position for a property, and only then compute the actual proposals.
+        * 
+        * @param document the document
+        * @param offset the offset into the document at which completion was
+        *        requested
+        * @param prefix the string immediately before the offset
+        * @return the list of {@link ICompletionProposal}s
+        */
+       private List proposeProperties(IDocument document, int offset,
+               String prefix) throws BadLocationException {
+               List proposals = new ArrayList();
+               if (isProperty(document, offset - 1)) {
+                       Image propertyIcon =
+                               CssUI.getDefault().getImageRegistry().get(
+                                       CssUI.ICON_PROPERTY);
+                       Image shorthandIcon =
+                               CssUI.getDefault().getImageRegistry().get(
+                                       CssUI.ICON_SHORTHAND);
+                       Collection propertyNames = profile.getProperties();
+                       for (Iterator i = propertyNames.iterator(); i.hasNext(); ) {
+                               String propertyName = (String) i.next();
+                               if (propertyName.startsWith(prefix)) {
+                                       Image icon = propertyIcon;
+                                       IPropertyInfo info = profile.getPropertyInfo(propertyName);
+                                       if (info.isShorthand()) {
+                                               icon = shorthandIcon;
+                                       }
+                                       ICompletionProposal proposal = new CompletionProposal(
+                                               propertyName, offset - prefix.length(),
+                                               prefix.length(), propertyName.length(), icon,
+                                               propertyName, null, info.getDescription());
+                                       proposals.add(proposal);
+                               }
+                       }
+               }
+               return proposals;
+       }
+
+       private List proposePseudoClasses(IDocument document, int offset,
+               String prefix) throws BadLocationException {
+               List proposals = new ArrayList();
+               if (isSelector(document, offset - 1)) {
+                       Image icon = CssUI.getDefault().getImageRegistry().get(
+                               CssUI.ICON_PSEUDO_CLASS);
+                       Collection pseudoClassNames = profile.getPseudoClassNames();
+                       for (Iterator i = pseudoClassNames.iterator(); i.hasNext(); ) {
+                               String pseudoClassName = (String) i.next();
+                               if (pseudoClassName.startsWith(prefix)) {
+                                       ICompletionProposal proposal = new CompletionProposal(
+                                               pseudoClassName, offset - prefix.length(),
+                                               prefix.length(), pseudoClassName.length(), icon,
+                                               pseudoClassName, null, null);
+                                       proposals.add(proposal);
+                               }
+                       }
+               }
+               return proposals;
+       }
+
+       private void sortProposals(List proposals) {
+               Collections.sort(proposals, new Comparator() {
+                       public int compare(Object o1, Object o2) {
+                               String s1 = ((ICompletionProposal) o1).getDisplayString();
+                               if (s1 == null) {
+                                       return -1;
+                               }
+                               String s2 = ((ICompletionProposal) o2).getDisplayString();
+                               return s1.compareToIgnoreCase(s2);
+                       }
+               });
+       }
+
+}