/* * $Id$ * * Copyright (c) 2006 by the TeXlipse team. * 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 net.sourceforge.texlipse.editor.scanner; import org.eclipse.jface.text.rules.ICharacterScanner; import org.eclipse.jface.text.rules.IPredicateRule; import org.eclipse.jface.text.rules.IToken; import org.eclipse.jface.text.rules.Token; /** * This class implements a rule which detects latex environments, i.e * \begin{environment} ... \end{environment} LaTeX permits whitespaces or line * feeds between \begin an {environment} and also this class. * * @author Boris von Loesch */ public class TexEnvironmentRule implements IPredicateRule { /** The token to be returned on success */ protected IToken fToken; /** The pattern's start sequence */ protected char[] fStartSequence; /** The pattern's end sequence */ protected char[] fEndSequence; /** The name of the environment */ protected char[][] fEnvName; protected boolean fStar; protected boolean fLastStar; protected int fLastEnv; public TexEnvironmentRule(String envName, IToken token) { this (envName, false, token); } public TexEnvironmentRule(String[] envNames, boolean star, IToken token) { fStartSequence = ("\\begin").toCharArray(); fEndSequence = ("\\end").toCharArray(); fToken = token; fEnvName = new char[envNames.length][]; for (int i = 0; i < envNames.length; i++) { fEnvName[i] = envNames[i].toCharArray(); } fStar = star; } /** * * @param envName Name of the environment * @param star if true, this environment also detects the stared version of the environment * @param token */ public TexEnvironmentRule(String envName, boolean star, IToken token) { fStartSequence = ("\\begin").toCharArray(); fEndSequence = ("\\end").toCharArray(); fToken = token; fEnvName = new char[1][]; fEnvName[0] = envName.toCharArray(); fStar = star; } /** * Evaluates this rules without considering any column constraints. Resumes * detection, i.e. look sonly for the end sequence required by this rule if * the <code>resume</code> flag is set. * * @param scanner the character scanner to be used * @param resume <code>true</code> if detection should be resumed, * <code>false</code> otherwise * @return the token resulting from this evaluation * @since 2.0 */ protected IToken doEvaluate(ICharacterScanner scanner, boolean resume) { fLastStar = true; fLastEnv = -1; if (resume) { //HACK, just read all available characters from the scanner. //This works very good in Eclipse 3.2 - 3.3 but maybe not in other versions. //Sadly there is no easy workaround that works as good as this int c; while ((c = scanner.read()) != ICharacterScanner.EOF) ; /* if (endSequenceDetected(scanner)) return fToken;*/ } else { int c = scanner.read(); if (c == fStartSequence[0]) { if (sequenceDetected(scanner, fStartSequence)) { if (endSequenceDetected(scanner)) return fToken; } } scanner.unread(); } return Token.UNDEFINED; } /* * @see IRule#evaluate(ICharacterScanner) */ public IToken evaluate(ICharacterScanner scanner) { return evaluate(scanner, false); } /** * Returns whether the end sequence was detected. * * * @param scanner the character scanner to be used * @return <code>true</code> if the end sequence has been detected */ protected boolean endSequenceDetected(ICharacterScanner scanner) { int c; int readChar = 1; while ((c = scanner.read()) != ICharacterScanner.EOF) { readChar++; if (fEndSequence.length > 0 && c == fEndSequence[0]) { // Check if the specified end sequence has been found. if (sequenceDetected(scanner, fEndSequence)) return true; } } unReadScanner(scanner, readChar); return false; } /** * Unreads a certain amount of charakters from the scanner * * @param scanner the charakter scanner * @param readChar the number of charakters to unread * @return false */ private boolean unReadScanner(ICharacterScanner scanner, int readChar) { for (int j = 0; j < readChar; j++) scanner.unread(); return false; } /** * Returns whether the next characters to be read by the character scanner * are an exact match of sequence + whitespaces + {envName} * * @param scanner the character scanner to be used * @param sequence either \begin or \end * @return <code>true</code> if the given sequence has been detected */ protected boolean sequenceDetected(ICharacterScanner scanner, char[] sequence) { int readChar = 0; for (int i = 1; i < sequence.length; i++) { int c = scanner.read(); readChar++; if (c != sequence[i]) { return unReadScanner(scanner, readChar); } } // Whitespaces int c; do { c = scanner.read(); readChar++; } while (isWhiteSpace((char) c)); // Now: {environment name} if (c != '{') { return unReadScanner(scanner, readChar); } if (fLastEnv == -1) { //Test if one of the environments fits boolean found = false; for (int j = 0; j < fEnvName.length; j++) { int readChar2 = 0; for (int i = 0; i < fEnvName[j].length; i++) { c = scanner.read(); readChar2++; if (c != fEnvName[j][i]) { unReadScanner(scanner, readChar2); break; } if (i == fEnvName[j].length - 1) found = true; } if (found) { fLastEnv = j; readChar += readChar2; break; } } if (!found) { return unReadScanner(scanner, readChar); } } else { //Test for environment fLastEnv for (int i = 0; i < fEnvName[fLastEnv].length; i++) { c = scanner.read(); readChar++; if (c != fEnvName[fLastEnv][i]) { return unReadScanner(scanner, readChar); } } } c = scanner.read(); readChar++; if (fStar && fLastStar && c == '*') { //Stared environment detected fLastStar = true; c = scanner.read(); readChar++; } else { fLastStar = false; } if (c != '}') { return unReadScanner(scanner, readChar); } return true; } /** * Returns true if c is either a whitespace, tab or linefeed * * @param c * @return */ private boolean isWhiteSpace(char c) { if (c == ' ' || c == '\n' || c == '\r' || c == '\t') return true; return false; } /* * @see IPredicateRule#evaluate(ICharacterScanner, boolean) * @since 2.0 */ public IToken evaluate(ICharacterScanner scanner, boolean resume) { return doEvaluate(scanner, resume); } /* * @see IPredicateRule#getSuccessToken() * @since 2.0 */ public IToken getSuccessToken() { return fToken; } }