2 * Copyright (c) 2003-2004 Christopher Lenz and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Common Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/cpl-v10.html
9 * Christopher Lenz - initial API and implementation
11 * $Id: CssContentAssistProcessor.java,v 1.1 2004-09-02 18:11:48 jsurfer Exp $
14 package net.sourceforge.phpeclipse.css.ui.internal.text;
16 import java.util.ArrayList;
17 import java.util.Collection;
18 import java.util.Collections;
19 import java.util.Comparator;
20 import java.util.Iterator;
21 import java.util.List;
23 import net.sourceforge.phpeclipse.core.model.ISourceReference;
24 import net.sourceforge.phpeclipse.css.core.internal.text.CssTextUtils;
25 import net.sourceforge.phpeclipse.css.core.model.IAtRule;
26 import net.sourceforge.phpeclipse.css.core.model.IDeclaration;
27 import net.sourceforge.phpeclipse.css.core.model.IPropertyInfo;
28 import net.sourceforge.phpeclipse.css.core.model.IRule;
29 import net.sourceforge.phpeclipse.css.core.model.IStyleRule;
30 import net.sourceforge.phpeclipse.css.core.model.IStyleSheet;
31 import net.sourceforge.phpeclipse.css.core.profiles.IProfile;
32 import net.sourceforge.phpeclipse.css.ui.CssUI;
33 import net.sourceforge.phpeclipse.css.ui.internal.CssDocumentProvider;
34 import net.sourceforge.phpeclipse.css.ui.internal.CssUIPreferences;
36 import org.eclipse.jface.preference.IPreferenceStore;
37 import org.eclipse.jface.text.BadLocationException;
38 import org.eclipse.jface.text.IDocument;
39 import org.eclipse.jface.text.IRegion;
40 import org.eclipse.jface.text.ITextViewer;
41 import org.eclipse.jface.text.contentassist.CompletionProposal;
42 import org.eclipse.jface.text.contentassist.ICompletionProposal;
43 import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
44 import org.eclipse.jface.text.contentassist.IContextInformation;
45 import org.eclipse.jface.text.contentassist.IContextInformationValidator;
46 import org.eclipse.swt.graphics.Image;
47 import org.eclipse.ui.texteditor.IDocumentProvider;
48 import org.eclipse.ui.texteditor.ITextEditor;
51 * Content assist processor for the CSS editor.
53 * TODO If a completion proposal is requested before the style sheet has been
54 * reconciled, we might not get any proposals, or they might be false.
56 public class CssContentAssistProcessor implements IContentAssistProcessor {
58 // Constants ---------------------------------------------------------------
60 private static final String AUTOACTIVATION_TRIGGERS =
61 CssUIPreferences.CONTENTASSIST_AUTOACTIVATION_TRIGGERS;
63 private static final String ORDER_PROPOSALS =
64 CssUIPreferences.CONTENTASSIST_ORDER_PROPOSALS;
66 // Instance Variables ------------------------------------------------------
69 * The preference store.
71 private IPreferenceStore store;
74 * The current CSS profile.
76 private IProfile profile;
79 * The associated text editor, if any.
81 private ITextEditor editor;
83 // Constructors ------------------------------------------------------------
88 * @param profile The CSS profile to use
90 public CssContentAssistProcessor(IPreferenceStore store, IProfile profile,
93 this.profile = profile;
97 // IContentAssistProcessor -------------------------------------------------
100 * @see IContentAssistProcessor#computeCompletionProposals
102 public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer,
103 int documentOffset) {
104 List retVal = Collections.EMPTY_LIST;
105 IDocument document = viewer.getDocument();
107 String prefix = getPrefix(document, documentOffset);
108 if (prefix.startsWith("@")) { //$NON-NLS-1$
109 retVal = proposeAtKeywords(document, documentOffset,
110 prefix.substring(1));
111 } else if (prefix.startsWith(":")) { //$NON-NLS-1$
112 retVal = proposePseudoClasses(document, documentOffset,
113 prefix.substring(1));
115 retVal = proposeProperties(document, documentOffset, prefix);
117 if ((store != null) && store.getBoolean(ORDER_PROPOSALS)) {
118 sortProposals(retVal);
120 } catch (BadLocationException e) {
122 "Unable to compute completion proposals", e); //$NON-NLS-1$
124 return (ICompletionProposal[]) retVal.toArray(
125 new ICompletionProposal[retVal.size()]);
129 * @see IContentAssistProcessor#computeContextInformation
131 public IContextInformation[] computeContextInformation(ITextViewer viewer,
132 int documentOffset) {
133 return new IContextInformation[0];
137 * @see IContentAssistProcessor#getCompletionProposalAutoActivationCharacters
139 public char[] getCompletionProposalAutoActivationCharacters() {
140 String chars = store.getString(AUTOACTIVATION_TRIGGERS);
144 return chars.toCharArray();
148 * @see IContentAssistProcessor#getContextInformationAutoActivationCharacters
150 public char[] getContextInformationAutoActivationCharacters() {
155 * @see IContentAssistProcessor#getErrorMessage
157 public String getErrorMessage() {
162 * @see IContentAssistProcessor#getContextInformationValidator
164 public IContextInformationValidator getContextInformationValidator() {
168 // Private Methods ---------------------------------------------------------
171 * Returns the part of the word immediately before the position at which
172 * content assist was requested. The prefix is lower-cased before it is
173 * returned, to enable case-insensitive matching.
175 * @param document the document
176 * @param offset the offset into the document
179 private String getPrefix(IDocument document, int offset) {
181 int startPos = offset;
182 while (startPos > 0) {
183 char c = document.getChar(startPos - 1);
184 if (!CssTextUtils.isCssIdentifierPart(c)
185 && (c != '@') && (c != ':')) {
189 if ((c == '@') || (c == ':')) {
193 if (startPos < offset) {
194 return document.get(startPos, offset - startPos).toLowerCase();
196 } catch (BadLocationException e) {
199 return ""; //$NON-NLS-1$
203 * Returns the parsed model of the document loaded in the editor, or
204 * <code>null</code> if the editor hasn't been set or the model couldn't be
207 * @return the parsed model
209 private IStyleSheet getStyleSheet() {
210 if (editor != null) {
211 IDocumentProvider provider = editor.getDocumentProvider();
212 if (provider instanceof CssDocumentProvider) {
213 return ((CssDocumentProvider) provider).getStyleSheet(
214 editor.getEditorInput());
220 public boolean isAtKeyword(IDocument document, int offset)
221 throws BadLocationException {
222 IStyleSheet styleSheet = getStyleSheet();
223 if (styleSheet != null) {
224 IRule rule = styleSheet.getRuleAt(offset);
225 if (rule instanceof IAtRule) {
226 ISourceReference name = ((IAtRule) rule).getName();
228 IRegion region = name.getSourceRegion();
229 for (int j = region.getOffset(); j < offset; j++) {
230 char ch = document.getChar(j);
231 if (!CssTextUtils.isCssIdentifierPart(ch)) {
237 } else if (rule == null) {
245 * Determines whether the document contains a property at the specified
246 * offset, or at least whether a property is theoretically allowed at that
249 * @param document the document
250 * @param offset the offset into the document
251 * @return <code>true</code> if the specified offset is a legal position for
252 * a property, <code>false</code> otherwise
253 * @throws BadLocationException if there was a problem accessing the
256 public boolean isProperty(IDocument document, int offset)
257 throws BadLocationException {
258 IStyleSheet styleSheet = getStyleSheet();
259 if (styleSheet != null) {
260 IRule rule = styleSheet.getRuleAt(offset);
262 IDeclaration declaration = rule.getDeclarationAt(offset);
263 if (declaration != null) {
264 IRegion region = declaration.getSourceRegion();
265 for (int j = region.getOffset(); j < offset; j++) {
266 if (document.getChar(j) == ':') {
272 IRegion region = rule.getSourceRegion();
273 for (int j = region.getOffset(); j < offset; j++) {
274 if (document.getChar(j) == '{') {
285 * Determines whether the document contains a selector at the specified
286 * offset, or at least whether a selector is theoretically allowed at that
289 public boolean isSelector(IDocument document, int offset)
290 throws BadLocationException {
291 IStyleSheet styleSheet = getStyleSheet();
292 if (styleSheet != null) {
293 IRule rule = styleSheet.getRuleAt(offset);
294 if (rule instanceof IStyleRule) {
295 ISourceReference selector = ((IStyleRule) rule).getSelector();
296 if (selector != null) {
297 IRegion region = selector.getSourceRegion();
298 for (int j = region.getOffset(); j < offset; j++) {
299 if (document.getChar(j) == '{') {
305 } else if (rule == null) {
312 private List proposeAtKeywords(IDocument document, int offset,
313 String prefix) throws BadLocationException {
314 List proposals = new ArrayList();
315 if (isAtKeyword(document, offset)) {
316 Image icon = CssUI.getDefault().getImageRegistry().get(
318 Collection atRuleNames = profile.getAtKeywords();
319 for (Iterator i = atRuleNames.iterator(); i.hasNext(); ) {
320 String atRuleName = (String) i.next();
321 if (atRuleName.startsWith(prefix)) {
322 ICompletionProposal proposal = new CompletionProposal(
323 atRuleName, offset - prefix.length(), prefix.length(),
324 atRuleName.length(), icon, atRuleName, null, null);
325 proposals.add(proposal);
333 * Computes the completion proposals for properties. A property may only
334 * appear at the left-hand side of a declaration, so we check whether the
335 * document offset for which the proposals were requested is a legal
336 * position for a property, and only then compute the actual proposals.
338 * @param document the document
339 * @param offset the offset into the document at which completion was
341 * @param prefix the string immediately before the offset
342 * @return the list of {@link ICompletionProposal}s
344 private List proposeProperties(IDocument document, int offset,
345 String prefix) throws BadLocationException {
346 List proposals = new ArrayList();
347 if (isProperty(document, offset - 1)) {
349 CssUI.getDefault().getImageRegistry().get(
350 CssUI.ICON_PROPERTY);
351 Image shorthandIcon =
352 CssUI.getDefault().getImageRegistry().get(
353 CssUI.ICON_SHORTHAND);
354 Collection propertyNames = profile.getProperties();
355 for (Iterator i = propertyNames.iterator(); i.hasNext(); ) {
356 String propertyName = (String) i.next();
357 if (propertyName.startsWith(prefix)) {
358 Image icon = propertyIcon;
359 IPropertyInfo info = profile.getPropertyInfo(propertyName);
360 if (info.isShorthand()) {
361 icon = shorthandIcon;
363 ICompletionProposal proposal = new CompletionProposal(
364 propertyName, offset - prefix.length(),
365 prefix.length(), propertyName.length(), icon,
366 propertyName, null, info.getDescription());
367 proposals.add(proposal);
374 private List proposePseudoClasses(IDocument document, int offset,
375 String prefix) throws BadLocationException {
376 List proposals = new ArrayList();
377 if (isSelector(document, offset - 1)) {
378 Image icon = CssUI.getDefault().getImageRegistry().get(
379 CssUI.ICON_PSEUDO_CLASS);
380 Collection pseudoClassNames = profile.getPseudoClassNames();
381 for (Iterator i = pseudoClassNames.iterator(); i.hasNext(); ) {
382 String pseudoClassName = (String) i.next();
383 if (pseudoClassName.startsWith(prefix)) {
384 ICompletionProposal proposal = new CompletionProposal(
385 pseudoClassName, offset - prefix.length(),
386 prefix.length(), pseudoClassName.length(), icon,
387 pseudoClassName, null, null);
388 proposals.add(proposal);
395 private void sortProposals(List proposals) {
396 Collections.sort(proposals, new Comparator() {
397 public int compare(Object o1, Object o2) {
398 String s1 = ((ICompletionProposal) o1).getDisplayString();
402 String s2 = ((ICompletionProposal) o2).getDisplayString();
403 return s1.compareToIgnoreCase(s2);