/******************************************************************************* * Copyright (c) 2006, 2015 Red Hat, Inc. * 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 * * Contributors: * Red Hat Incorporated - initial API and implementation *******************************************************************************/ package org.eclipse.cdt.autotools.ui.editors; import java.util.ArrayList; import java.util.List; 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.SingleLineRule; import org.eclipse.jface.text.rules.Token; public class RecursiveSingleLineRule extends SingleLineRule { private List<IRule> rules; private int evalIndex; private int startIndex; private int endIndex; private int endBoundary; private String startSequence; private String endSequence; /** * Creates a rule for the given starting and ending sequence * which, if detected, will return the specified token. * * @param startSequence the pattern's start sequence * @param endSequence the pattern's end sequence * @param token the token to be returned on success */ public RecursiveSingleLineRule(String startSequence, String endSequence, IToken token) { this(startSequence, endSequence, token, (char) 0); } /** * Creates a rule for the given starting and ending sequence * which, if detected, will return the specified token. * Any character which follows the given escape character * will be ignored. * * @param startSequence the pattern's start sequence * @param endSequence the pattern's end sequence * @param token the token to be returned on success * @param escapeCharacter the escape character */ public RecursiveSingleLineRule(String startSequence, String endSequence, IToken token, char escapeCharacter) { this(startSequence, endSequence, token, escapeCharacter, false); } /** * Creates a rule for the given starting and ending sequence * which, if detected, will return the specified token. Alternatively, the * line can also be ended with the end of the file. * Any character which follows the given escape character * will be ignored. * * @param startSequence the pattern's start sequence * @param endSequence the pattern's end sequence * @param token the token to be returned on success * @param escapeCharacter the escape character * @param breaksOnEOF indicates whether the end of the file successfully terminates this rule * @since 2.1 */ public RecursiveSingleLineRule(String startSequence, String endSequence, IToken token, char escapeCharacter, boolean breaksOnEOF) { super(startSequence, endSequence, token, escapeCharacter, breaksOnEOF); this.startSequence = startSequence; this.endSequence = endSequence; rules = new ArrayList<>(); startIndex = 0; endIndex = 0; } /** * Creates a rule for the given starting and ending sequence * which, if detected, will return the specified token. Alternatively, the * line can also be ended with the end of the file. * Any character which follows the given escape character * will be ignored. In addition, an escape character immediately before an * end of line can be set to continue the line. * * @param startSequence the pattern's start sequence * @param endSequence the pattern's end sequence * @param token the token to be returned on success * @param escapeCharacter the escape character * @param breaksOnEOF indicates whether the end of the file successfully terminates this rule * @param escapeContinuesLine indicates whether the specified escape character is used for line * continuation, so that an end of line immediately after the escape character does not * terminate the line, even if <code>breakOnEOL</code> is true * @since 3.0 */ public RecursiveSingleLineRule(String startSequence, String endSequence, IToken token, char escapeCharacter, boolean breaksOnEOF, boolean escapeContinuesLine) { super(startSequence, endSequence, token, escapeCharacter, breaksOnEOF, escapeContinuesLine); this.startSequence = startSequence; this.endSequence = endSequence; rules = new ArrayList<>(); startIndex = 0; endIndex = 0; } public void addRule(SingleLineRule rule) { rules.add(rule); } @Override public IToken getSuccessToken() { // We need to be aware of what success token we are referring to. // The current internal rule index will help us determine which // one. if (evalIndex < rules.size()) { SingleLineRule x = (SingleLineRule)rules.get(evalIndex); return x.getSuccessToken(); } return super.getSuccessToken(); } protected void backupScanner(ICharacterScanner scanner, int position) { int count = scanner.getColumn() - position; while (count-- > 0) scanner.unread(); } @Override public IToken evaluate(ICharacterScanner scanner, boolean resume) { int column = scanner.getColumn(); // Check if we are at EOF, in which case rules don't hold if (column < 0) return Token.UNDEFINED; if (!resume) { evalIndex = 0; // Check if we are within outer rule boundaries. if (column >= endIndex || column < startIndex) { // If not, then we should evaluate to see if the // outer rule is true starting at the current position. startIndex = scanner.getColumn(); if (super.evaluate(scanner, false) != Token.UNDEFINED) { // Outer rule is true for a section. Now we can // set the boundaries for the internal rules. // End boundary for internal rules is the start of // the end sequence. endIndex = scanner.getColumn(); endBoundary = endIndex - endSequence.length(); // Back up scanner to just after start sequence. backupScanner(scanner, startIndex + startSequence.length()); return super.getSuccessToken(); } else // Outer rule doesn't hold. return Token.UNDEFINED; } } // At this point, we want to subdivide up the area covered by the // outer rule into success tokens for internal areas separated by // areas of the outer rule. int start = scanner.getColumn(); column = start; while (column < endBoundary) { while (evalIndex < rules.size()) { SingleLineRule x = (SingleLineRule)rules.get(evalIndex); IToken token = x.evaluate(scanner, false); if (!token.isUndefined()) { // Found internal token. If we had to read to get // to the start of the internal token, then back up // the scanner to the start of the internal token and // return the initial read area as part of an outer token. // Otherwise, return the internal token. if (column == start) { evalIndex = 0; return token; } else { backupScanner(scanner, column); return super.getSuccessToken(); } } ++evalIndex; } evalIndex = 0; scanner.read(); ++column; } // Outside internal area. Read until end of outer area and return // outer token. while (column++ < endIndex) scanner.read(); startIndex = 0; endIndex = 0; return super.getSuccessToken(); } }