/******************************************************************************* * Copyright (c) 2005, 2007 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 ******************************************************************************/ package org.eclipse.dltk.ui.text.rules; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import org.eclipse.core.runtime.Assert; import org.eclipse.dltk.compiler.util.Util; import org.eclipse.jface.text.rules.ICharacterScanner; import org.eclipse.jface.text.rules.IRule; import org.eclipse.jface.text.rules.IToken; import org.eclipse.jface.text.rules.IWordDetector; import org.eclipse.jface.text.rules.Token; import org.eclipse.jface.text.rules.WordRule; /** * An implementation of <code>IRule</code> capable of detecting script words. * * <p> * This class is a direct copy of {@link WordRule} capable of tracking the 'last * seen' word and returning a token that differs from the default token for this * rule when the next word is encountered. For instance, this could be used to * provide syntax hightlighting for method declarations. * </p> * * <p> * If an <code>IScriptWordDetector</code> implementation is used, the rule * will also check that character prior to the word start character is valid for * the start of the word to occur. For instance, this could be used to prevent * method names that also match builtin keywords from being hightlighted as * such. * </p> * * @see IWordDetector * @see IScriptWordDetector */ public class ScriptWordRule implements IRule { /** Internal setting for the un-initialized column constraint */ protected static final int UNDEFINED = -1; /** The word detector used by this rule */ protected IWordDetector fDetector; /** * The default token to be returned on success and if nothing else has been * specified. */ protected IToken fDefaultToken; /** The column constraint */ protected int fColumn = UNDEFINED; /** The table of predefined words and token for this rule */ protected Map<String, IToken> fWords = new HashMap<String, IToken>(); /** Buffer used for pattern detection */ private StringBuffer fBuffer = new StringBuffer(); private boolean fIgnoreCase = false; private int fLastSeenEnd = 0; private String fLastSeen = Util.EMPTY_STRING; private Map<String, IToken> fNext = new HashMap<String, IToken>(); /** * Creates a rule which, with the help of an word detector, will return the * token associated with the detected word. If no token has been associated, * the scanner will be rolled back and an undefined token will be returned * in order to allow any subsequent rules to analyze the characters. * * @param detector * the word detector to be used by this rule, may not be * <code>null</code> * @see #addWord(String, IToken) */ public ScriptWordRule(IWordDetector detector) { this(detector, Token.UNDEFINED, false); } /** * Creates a rule which, with the help of a word detector, will return the * token associated with the detected word. If no token has been associated, * the specified default token will be returned. * * @param detector * the word detector to be used by this rule, may not be * <code>null</code> * @param defaultToken * the default token to be returned on success if nothing else is * specified, may not be <code>null</code> * @see #addWord(String, IToken) */ public ScriptWordRule(IWordDetector detector, IToken defaultToken) { this(detector, defaultToken, false); } /** * Creates a rule which, with the help of a word detector, will return the * token associated with the detected word. If no token has been associated, * the specified default token will be returned. * * @param detector * the word detector to be used by this rule, may not be * <code>null</code> * @param defaultToken * the default token to be returned on success if nothing else is * specified, may not be <code>null</code> * @param ignoreCase * the case sensitivity associated with this rule * @see #addWord(String, IToken) */ public ScriptWordRule(IWordDetector detector, IToken defaultToken, boolean ignoreCase) { Assert.isNotNull(detector); Assert.isNotNull(defaultToken); fDetector = detector; fDefaultToken = defaultToken; fIgnoreCase = ignoreCase; } /** * Adds a word and the token to be returned if it is detected. * * @param word * the word this rule will search for, may not be * <code>null</code> * @param token * the token to be returned if the word has been found, may not * be <code>null</code> */ public void addWord(String word, IToken token) { Assert.isNotNull(word); Assert.isNotNull(token); fWords.put(word, token); } /** * Add a word to be treated in a 'last seen' context. * * <p> * If the specified word was the 'last seen', the specified token will be * returned as the token for the next detected word. * </p> * * @param word * 'last seen' word look for, may not be <code>null</code> * @param token * the token to be returned if the 'last seen' word is detected * */ public void addNextTokenAfterSeen(String word, IToken token) { Assert.isNotNull(word); Assert.isNotNull(token); fNext.put(word, token); } /** * Sets a column constraint for this rule. If set, the rule's token will * only be returned if the pattern is detected starting at the specified * column. If the column is smaller then 0, the column constraint is * considered removed. * * @param column * the column in which the pattern starts */ public void setColumnConstraint(int column) { if (column < 0) column = UNDEFINED; fColumn = column; } public IToken evaluate(ICharacterScanner scanner) { // don't unwind the scanner if we're at the beginning if (fDetector instanceof IScriptWordDetector && scanner.getColumn() > 0) { scanner.unread(); int c = scanner.read(); if (!((IScriptWordDetector) fDetector).isPriorCharValid((char) c)) { return Token.UNDEFINED; } } int c = scanner.read(); if (fDetector.isWordStart((char) c)) { if (fColumn == UNDEFINED || (fColumn == scanner.getColumn() - 1)) { fBuffer.setLength(0); do { fBuffer.append((char) c); c = scanner.read(); } while (c != ICharacterScanner.EOF && fDetector.isWordPart((char) c)); scanner.unread(); String buffer = fBuffer.toString(); // // our swap criteria: // // 1) we have a mapping for the 'lastSeen' word // 2) the current word doesn't start w/ the 'lastSeen' // 3) the current position in the scanner is > the end column of // 'lastSeen' // if (fNext.containsKey(fLastSeen) && !buffer.startsWith(fLastSeen) && scanner.getColumn() > fLastSeenEnd) { IToken replace = fNext.get(fLastSeen); fLastSeen = buffer; fLastSeenEnd = scanner.getColumn(); return replace; } IToken token = fWords.get(buffer); if (fIgnoreCase) { Iterator<String> iter = fWords.keySet().iterator(); while (iter.hasNext()) { String key = iter.next(); if (buffer.equalsIgnoreCase(key)) { token = fWords.get(key); break; } } } if (token != null) { fLastSeen = buffer; fLastSeenEnd = scanner.getColumn(); return token; } if (fDefaultToken.isUndefined()) unreadBuffer(scanner); return fDefaultToken; } } scanner.unread(); return Token.UNDEFINED; } /** * Returns the characters in the buffer to the scanner. * * @param scanner * the scanner to be used */ protected void unreadBuffer(ICharacterScanner scanner) { for (int i = fBuffer.length() - 1; i >= 0; i--) scanner.unread(); } }