Improved PHP end tag (i.e. ?> ) syntax highlighting after operators
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpeclipse / phpeditor / php / PHPCodeScanner.java
1 /**********************************************************************
2  Copyright (c) 2000, 2002 IBM Corp. 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
7
8  Contributors:
9  IBM Corporation - Initial implementation
10  www.phpeclipse.de
11  **********************************************************************/
12 package net.sourceforge.phpeclipse.phpeditor.php;
13
14 import java.util.ArrayList;
15 import java.util.List;
16
17 import net.sourceforge.phpdt.internal.ui.text.AbstractJavaScanner;
18 import net.sourceforge.phpdt.ui.text.IColorManager;
19 import net.sourceforge.phpeclipse.IPreferenceConstants;
20 import net.sourceforge.phpeclipse.phpeditor.PHPSyntaxRdr;
21 import net.sourceforge.phpeclipse.phpeditor.util.PHPWhitespaceDetector;
22 import net.sourceforge.phpeclipse.phpeditor.util.PHPWordDetector;
23
24 import org.eclipse.jface.preference.IPreferenceStore;
25 import org.eclipse.jface.text.rules.EndOfLineRule;
26 import org.eclipse.jface.text.rules.ICharacterScanner;
27 import org.eclipse.jface.text.rules.IRule;
28 import org.eclipse.jface.text.rules.IToken;
29 import org.eclipse.jface.text.rules.IWordDetector;
30 import org.eclipse.jface.text.rules.MultiLineRule;
31 import org.eclipse.jface.text.rules.Token;
32 import org.eclipse.jface.text.rules.WhitespaceRule;
33 import org.eclipse.jface.text.rules.WordRule;
34
35 /**
36  * PHP Code Scanner
37  */
38 public class PHPCodeScanner extends AbstractJavaScanner {
39
40   /**
41    * Rule to detect java operators.
42    * 
43    * @since 3.0
44    */
45   protected class OperatorRule implements IRule {
46
47     /** Java operators */
48     private final char[] PHP_OPERATORS = {
49         ';',
50         '(',
51         ')',
52         '.',
53         '=',
54         '/',
55         '\\',
56         '+',
57         '-',
58         '*',
59         '[',
60         ']',
61         '<',
62         '>',
63         ':',
64         '?',
65         '!',
66         ',',
67         '|',
68         '&',
69         '^',
70         '%',
71         '~',
72         '@' };
73
74     /** Token to return for this rule */
75     private final IToken fToken;
76
77     /** Token to return for braces */
78     private final IToken fTokenBraces;
79
80     /**
81      * Creates a new operator rule.
82      * 
83      * @param token
84      *          Token to use for this rule
85      */
86     public OperatorRule(IToken token, IToken tokenBraces) {
87       fToken = token;
88       fTokenBraces = tokenBraces;
89
90     }
91
92     /**
93      * Is this character an operator character?
94      * 
95      * @param character
96      *          Character to determine whether it is an operator character
97      * @return <code>true</code> iff the character is an operator, <code>false</code> otherwise.
98      */
99     public boolean isOperator(char character) {
100       for (int index = 0; index < PHP_OPERATORS.length; index++) {
101         if (PHP_OPERATORS[index] == character)
102           return true;
103       }
104       return false;
105     }
106
107     /*
108      * @see org.eclipse.jface.text.rules.IRule#evaluate(org.eclipse.jface.text.rules.ICharacterScanner)
109      */
110     public IToken evaluate(ICharacterScanner scanner) {
111
112       int character = scanner.read();
113       if (character == '{' || character == '}') {
114         return fTokenBraces;
115       }
116       if (isOperator((char) character)) {
117         int lastCharacter = character;
118         character = scanner.read();
119         if (!isOperator((char) character)) {
120           scanner.unread();
121           return fToken;
122         }
123         if (checkPHPTag(scanner, lastCharacter, character)) {
124           return Token.UNDEFINED;
125         }
126         do {
127           lastCharacter = character;
128           character = scanner.read();
129           if (checkPHPTag(scanner, lastCharacter, character)) {
130             return fToken;
131           }
132         } while (isOperator((char) character));
133         scanner.unread();
134         return fToken;
135       } else {
136         scanner.unread();
137         return Token.UNDEFINED;
138       }
139     }
140
141     /**
142      * Check if lastCharacter/character are a PHP start or end token ( &lt;? ... ?&gt; )
143      * 
144      * @param scanner
145      * @param lastCharacter
146      * @param character
147      * @return
148      */
149     private boolean checkPHPTag(ICharacterScanner scanner, int lastCharacter, int character) {
150       if (lastCharacter == '<' && character == '?') {
151         scanner.unread();
152         scanner.unread();
153         return true;
154       } else if (lastCharacter == '?' && character == '>') {
155         scanner.unread();
156         scanner.unread();
157         return true;
158       }
159       return false;
160     }
161   }
162
163   private class PHPWordRule extends WordRule {
164     private StringBuffer fBuffer = new StringBuffer();
165
166     public PHPWordRule(IWordDetector detector) {
167       super(detector, Token.UNDEFINED);
168     }
169
170     public PHPWordRule(IWordDetector detector, IToken defaultToken) {
171       super(detector, defaultToken);
172     }
173
174     public IToken evaluate(ICharacterScanner scanner) {
175       int c = scanner.read();
176       boolean isVariable = false;
177       if (c == '<') {
178         c = scanner.read();
179         if (c != '?') {
180           scanner.unread();
181           scanner.unread();
182           return Token.UNDEFINED;
183         } else {
184           c = scanner.read();
185           if (c == '=') {  // <?=
186             return getToken(IPreferenceConstants.PHP_TAG);
187           }
188           if (c != 'p' && c != 'P') {
189             scanner.unread();
190             return getToken(IPreferenceConstants.PHP_TAG);
191           } else {
192             c = scanner.read();
193             if (c != 'h' && c != 'H') {
194               scanner.unread();
195               scanner.unread();
196               return getToken(IPreferenceConstants.PHP_TAG);
197             } else {
198               c = scanner.read();
199               if (c != 'p' && c != 'P') {
200                 scanner.unread();
201                 scanner.unread();
202                 scanner.unread();
203                 return getToken(IPreferenceConstants.PHP_TAG);
204               } else {
205                 return getToken(IPreferenceConstants.PHP_TAG);
206               }
207             }
208           }
209         }
210       }
211       if (c == '?') {
212         c = scanner.read();
213         if (c == '>') {
214           return getToken(IPreferenceConstants.PHP_TAG);
215         }
216         scanner.unread();
217         scanner.unread();
218         return Token.UNDEFINED;
219       }
220       if (fDetector.isWordStart((char) c)) {
221         if (c == '$') {
222           isVariable = true;
223         }
224         if (fColumn == UNDEFINED || (fColumn == scanner.getColumn() - 1)) {
225
226           fBuffer.setLength(0);
227           do {
228             fBuffer.append((char) c);
229             c = scanner.read();
230           } while (c != ICharacterScanner.EOF && fDetector.isWordPart((char) c));
231           scanner.unread();
232
233           if (isVariable) {
234             return getToken(IPreferenceConstants.PHP_VARIABLE);
235           }
236           IToken token = (IToken) fWords.get(fBuffer.toString());
237           if (token != null)
238             return token;
239
240           if (fDefaultToken.isUndefined())
241             unreadBuffer(scanner);
242
243           return fDefaultToken;
244         }
245       }
246
247       scanner.unread();
248       return Token.UNDEFINED;
249     }
250   }
251
252   //private PHPColorProvider fColorProvider;
253
254   private static String[] fgTokenProperties = {
255       IPreferenceConstants.PHP_MULTILINE_COMMENT,
256       IPreferenceConstants.PHP_SINGLELINE_COMMENT,
257       IPreferenceConstants.PHP_TAG,
258       IPreferenceConstants.PHP_KEYWORD,
259       IPreferenceConstants.PHP_FUNCTIONNAME,
260       IPreferenceConstants.PHP_VARIABLE,
261       IPreferenceConstants.PHP_STRING,
262       IPreferenceConstants.PHP_TYPE,
263       IPreferenceConstants.PHP_CONSTANT,
264       IPreferenceConstants.PHP_DEFAULT,
265       IPreferenceConstants.PHP_OPERATOR,
266       IPreferenceConstants.PHP_BRACE_OPERATOR,
267       IPreferenceConstants.PHP_KEYWORD_RETURN };
268
269   /**
270    * Creates a PHP code scanner
271    */
272   // public PHPCodeScanner(JavaColorManager provider, IPreferenceStore store) {
273   public PHPCodeScanner(IColorManager manager, IPreferenceStore store) {
274     super(manager, store);
275     initialize();
276   }
277
278   /*
279    * @see AbstractJavaScanner#getTokenProperties()
280    */
281   protected String[] getTokenProperties() {
282     return fgTokenProperties;
283   }
284
285   /*
286    * @see AbstractJavaScanner#createRules()
287    */
288   protected List createRules() {
289     List rules = new ArrayList();
290     Token token = getToken(IPreferenceConstants.PHP_SINGLELINE_COMMENT);
291     // Add rule for single line comments.
292     rules.add(new EndOfLineRule("//", token)); //$NON-NLS-1$
293     rules.add(new EndOfLineRule("#", token)); //$NON-NLS-1$
294     // Add rule for strings and character constants.
295     token = getToken(IPreferenceConstants.PHP_STRING);
296     rules.add(new MultiLineRule("\"", "\"", token, '\\')); //$NON-NLS-2$ //$NON-NLS-1$
297     rules.add(new MultiLineRule("`", "`", token, '\\')); //$NON-NLS-2$ //$NON-NLS-1$
298     rules.add(new MultiLineRule("'", "'", token, '\\')); //$NON-NLS-2$ //$NON-NLS-1$
299
300     token = getToken(IPreferenceConstants.PHP_MULTILINE_COMMENT);
301     rules.add(new MultiLineRule("/*", "*/", token)); //$NON-NLS-2$ //$NON-NLS-1$
302     // Add generic whitespace rule.
303     rules.add(new WhitespaceRule(new PHPWhitespaceDetector()));
304     // Add word rule for keywords, types, and constants.
305     token = getToken(IPreferenceConstants.PHP_DEFAULT);
306     PHPWordRule wordRule = new PHPWordRule(new PHPWordDetector(), token);
307
308     Token keyword = getToken(IPreferenceConstants.PHP_KEYWORD);
309     Token functionName = getToken(IPreferenceConstants.PHP_FUNCTIONNAME);
310     Token type = getToken(IPreferenceConstants.PHP_TYPE);
311     Token constant = getToken(IPreferenceConstants.PHP_CONSTANT);
312
313     ArrayList buffer = PHPSyntaxRdr.getSyntaxData();
314     //  String strbuffer = null; unused
315     PHPElement elbuffer = null;
316     String name;
317     for (int i = 0; i < buffer.size(); i++) {
318       //    while ((buffer != null)
319       //      && (!buffer.isEmpty()
320       //        && ((elbuffer = (PHPElement) buffer.remove(0)) != null))) {
321       elbuffer = (PHPElement) buffer.get(i);
322       if (elbuffer instanceof PHPKeyword) {
323         name = ((PHPKeyword) elbuffer).getName();
324         if (!name.equals("return")) {
325           wordRule.addWord(name, keyword);
326         }
327       } else if (elbuffer instanceof PHPFunction) {
328         wordRule.addWord(((PHPFunction) elbuffer).getName(), functionName);
329       } else if (elbuffer instanceof PHPType) {
330         wordRule.addWord(elbuffer.getName(), type);
331       } else if (elbuffer instanceof PHPConstant) {
332         wordRule.addWord(elbuffer.getName(), constant);
333       }
334     }
335
336     // Add word rule for keyword 'return'.
337     token = getToken(IPreferenceConstants.PHP_KEYWORD_RETURN);
338     wordRule.addWord("return", token);
339
340     //  Add rule for operators and brackets (at the end !)
341     rules.add(new OperatorRule(getToken(IPreferenceConstants.PHP_OPERATOR), getToken(IPreferenceConstants.PHP_BRACE_OPERATOR)));
342
343     rules.add(wordRule);
344
345     setDefaultReturnToken(getToken(IPreferenceConstants.PHP_DEFAULT));
346     return rules;
347   }
348 }