X-Git-Url: http://secure.phpeclipse.com 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 index 0000000..c3a712e --- /dev/null +++ b/archive/net.sourceforge.phpeclipse.css.ui/src/net/sourceforge/phpeclipse/css/ui/internal/text/CssContentAssistProcessor.java @@ -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 + * null 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 true if the specified offset is a legal position for + * a property, false 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); + } + }); + } + +}