Here 'tis.
On 3/20/07, Mirko Stocker <[EMAIL PROTECTED]> wrote:
On Monday 19 March 2007 19:27:43 rover rhubarb wrote:
> So this is all local at the moment, how can I best get it to you to
> contribute it? Files? Diffs? Patches? Check in?
I think the easiest way for us to take a look at your code would be to send
patches / diffs, so we can review and apply it. You can also open a ticket[1]
and append the patch there if you want.
Thank you!
Mirko
[1] http://rubyeclipse.gr-ruby.org/newticket
Index:
C:/ruby/rdtworkspace/org.rubypeople.rdt.ui/src/org/rubypeople/rdt/internal/ui/text/RubyPairMatcher.java
===================================================================
---
C:/ruby/rdtworkspace/org.rubypeople.rdt.ui/src/org/rubypeople/rdt/internal/ui/text/RubyPairMatcher.java
(revision 1938)
+++
C:/ruby/rdtworkspace/org.rubypeople.rdt.ui/src/org/rubypeople/rdt/internal/ui/text/RubyPairMatcher.java
(working copy)
@@ -73,71 +73,59 @@
*/
public void clear() {
}
-
+
+
+ /**
+ * Checks the current offset to see if it is a bracketing character or
keyword, then returns
+ * true if it can find a matching pair for it. Importantly, it also sets
the fields fStartPos and
+ * fEndPos to the first character of the opening bracket or keyword and
the last character of the
+ * closing bracket or keyword, respectively.
+ * @return true if we can find a matching pair for the character or
keyword at the current position.
+ */
protected boolean matchPairsAt() {
-
- int i;
- int pairIndex1= fPairs.length;
- int pairIndex2= fPairs.length;
-
fStartPos= -1;
fEndPos= -1;
- // get the char preceding the start position
- try {
-
- char prevChar= fDocument.getChar(Math.max(fOffset - 1, 0));
- // search for opening peer character next to the activation point
- for (i= 0; i < fPairs.length; i= i + 2) {
- if (prevChar == fPairs[i]) {
- fStartPos= fOffset - 1;
- pairIndex1= i;
- }
- }
-
- // search for closing peer character next to the activation point
- for (i= 1; i < fPairs.length; i= i + 2) {
- if (prevChar == fPairs[i]) {
- fEndPos= fOffset - 1;
- pairIndex2= i;
- }
- }
-
- if (fEndPos > -1) {
- fAnchor= RIGHT;
- fStartPos= searchForOpeningPeer(fEndPos, fPairs[pairIndex2 -
1], fPairs[pairIndex2], fDocument);
- if (fStartPos > -1)
- return true;
- else
- fEndPos= -1;
- } else if (fStartPos > -1) {
- fAnchor= LEFT;
- fEndPos= searchForClosingPeer(fStartPos, fPairs[pairIndex1],
fPairs[pairIndex1 + 1], fDocument);
- if (fEndPos > -1)
- return true;
- else
- fStartPos= -1;
- }
-
- } catch (BadLocationException x) {
- }
-
+ RubyHeuristicScanner scanner;
+ try {
+ scanner = new RubyHeuristicScanner(fDocument,
IRubyPartitions.RUBY_PARTITIONING,
+ TextUtilities.getContentType(fDocument,
IRubyPartitions.RUBY_PARTITIONING, fOffset, false));
+
+ String contentType =
TextUtilities.getContentType(fDocument, IRubyPartitions.RUBY_PARTITIONING,
fOffset, false);
+ if
(contentType.equals(IRubyPartitions.RUBY_SINGLE_LINE_COMMENT) ||
+
contentType.equals(IRubyPartitions.RUBY_MULTI_LINE_COMMENT)) {
+ return false; // don't try to match within
comments
+ }
+
+ int token = scanner.tokenAt(fOffset); // only returns a token
if there really is one at the position
+
+ if (RubyHeuristicScanner.isOpeningToken(token)) {
+ fStartPos = scanner.getPosition(); // scanner put the
pos at the start in the tokenAt call
+ fAnchor= LEFT;
+ fEndPos = scanner.findClosingPeerToken(fStartPos,
token);
+ if (fEndPos == RubyHeuristicScanner.NOT_FOUND) {
+ fStartPos = -1;
+ fEndPos = -1;
+ return false;
+ }
+ return true;
+ }
+ else if (RubyHeuristicScanner.isClosingToken(token)) {
+ fEndPos = scanner.getPosition();
+ fAnchor = RIGHT;
+ fStartPos = scanner.findOpeningPeerToken(fEndPos,
token);
+ if (fStartPos == RubyHeuristicScanner.NOT_FOUND) {
+ fStartPos = -1;
+ fEndPos = -1;
+ return false;
+ }
+ return true;
+ }
+ } catch (BadLocationException e) {
+ return false;
+ }
+
+ // not on a token with a pair
return false;
}
-
- protected int searchForClosingPeer(int offset, char openingPeer, char
closingPeer, IDocument document) throws BadLocationException {
- RubyHeuristicScanner scanner= new RubyHeuristicScanner(document,
IRubyPartitions.RUBY_PARTITIONING, TextUtilities.getContentType(document,
IRubyPartitions.RUBY_PARTITIONING, offset, false));
- return scanner.findClosingPeer(offset + 1, openingPeer, closingPeer);
- }
-
-
- protected int searchForOpeningPeer(int offset, char openingPeer, char
closingPeer, IDocument document) throws BadLocationException {
- RubyHeuristicScanner scanner= new RubyHeuristicScanner(document,
IRubyPartitions.RUBY_PARTITIONING, TextUtilities.getContentType(document,
IRubyPartitions.RUBY_PARTITIONING, offset, false));
- int peer= scanner.findOpeningPeer(offset - 1, openingPeer,
closingPeer);
- if (peer == RubyHeuristicScanner.NOT_FOUND)
- return -1;
- return peer;
- }
-
-
}
Index:
C:/ruby/rdtworkspace/org.rubypeople.rdt.ui/src/org/rubypeople/rdt/internal/ui/text/RubyHeuristicScanner.java
===================================================================
---
C:/ruby/rdtworkspace/org.rubypeople.rdt.ui/src/org/rubypeople/rdt/internal/ui/text/RubyHeuristicScanner.java
(revision 1938)
+++
C:/ruby/rdtworkspace/org.rubypeople.rdt.ui/src/org/rubypeople/rdt/internal/ui/text/RubyHeuristicScanner.java
(working copy)
@@ -10,7 +10,10 @@
*******************************************************************************/
package org.rubypeople.rdt.internal.ui.text;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
import org.eclipse.jface.text.Assert;
import org.eclipse.jface.text.BadLocationException;
@@ -40,7 +43,137 @@
*/
public static final int UNBOUND= -2;
+
+ /**
+ * Pairs of tokens to be matched.
+ * This array is defined such that every second entry is a closing pair
token for the entry bofore it,
+ * so only add entries in pairs.
+ */
+ public static final int[] BRACKET_TOKENS = new int[] { TokenLBRACE,
TokenRBRACE,
+
TokenLBRACKET, TokenRBRACKET,
+
TokenLPAREN, TokenRPAREN,
+ TokenDEF,
TokenEND,
+ TokenDO,
TokenEND,
+ TokenBEGIN,
TokenEND,
+ TokenIF,
TokenEND,
+ TokenUNLESS,
TokenEND,
+ TokenCLASS,
TokenEND,
+ TokenMODULE,
TokenEND
+ };
+
+ /** A hashmap that associates tokens with their matching closing or
opening tokens */
+ private static HashMap fMatchingPeers;
+
+ static {
+ fMatchingPeers = new HashMap();
+ buildMatchMap(fMatchingPeers, true);
+ buildMatchMap(fMatchingPeers, false);
+ }
+
+ /**
+ * Convenience method for stepping through the bracket tokens array,
looking
+ * for a token either among the opening or the closing tokens.
+ */
+ private static boolean _findBracketToken(int token, int startIndex, int
step) {
+ for (int i = startIndex; i < BRACKET_TOKENS.length; i += step) {
+ if (token == BRACKET_TOKENS[i]) return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the token represents the opening or closing of a
bracket or scope.
+ * E.g. {, }, [, ], (, def, class, begin, if, end, ...
+ *
+ * @param token the token to test
+ * @return true if it is a character or keyword that brackets a scope
+ */
+ public static boolean isBracketToken(int token) {
+ return _findBracketToken(token, 0, 1);
+ }
+
+ /**
+ * Returns true if the token represents the opening of a bracket or
scope.
+ * E.g. {, [, (, def, class, begin, if, ...
+ *
+ * @param token the token to test
+ * @return true if it is a character or keyword that opens a scope
+ */
+ public static boolean isOpeningToken(int token) {
+ return _findBracketToken(token, 0, 2);
+ }
+
+ /**
+ * Returns true if the token represents the closing of a bracket or
scope.
+ * E.g. }, ], ), end, ...
+ *
+ * @param token the token to test
+ * @return true if it is a character or keyword that closes a scope
+ */
+ public static boolean isClosingToken(int token) {
+ return _findBracketToken(token, 1, 2);
+ }
+
+ /**
+ * Builds up a map of tokens that close the scope opened by each token.
+ * If forward is false it builds a list of tokens that open each token.
+ * @param matchMap the hashmap to be filled
+ * @param forward true to build a map of closing tokens
+ */
+ private static void buildMatchMap(HashMap matchMap, boolean forward) {
+ int startIndex, matchOffset;
+ if (forward) {
+ startIndex = 0;
+ matchOffset = 1;
+ }
+ else {
+ startIndex = 1;
+ matchOffset = -1;
+ }
+
+ List matchList;
+
+ for (int i= startIndex; i < BRACKET_TOKENS.length; i+= 2) {
+ int token = BRACKET_TOKENS[i];
+ int matchingToken = BRACKET_TOKENS[i + matchOffset];
+
+ matchList =
(ArrayList)matchMap.get(Integer.valueOf(token));
+ if (matchList == null) {
+ matchList = new ArrayList();
+ matchMap.put(Integer.valueOf(token), matchList);
+ }
+ matchList.add(Integer.valueOf(matchingToken));
+ }
+ }
+
+ /**
+ * Returns an arry of tokens that are either the opening or closing
pairs for the specified token.
+ * For example: passing in TokenLBRACE (token for '{') will return an
array of one element containing
+ * TokenRBRACE (token for '}').
+ * Passing in TokenBEGIN (token for
'begin') will return an array of one element containing
+ * TokenEND (token for 'end').
+ * Passing in TokenEND (token for 'begin')
will return an array containing all of the tokens
+ * that open a scope that can end with
'end', such as: TokenBEGIN, TokenIF, TokenDO, etc.
+ *
+ * Note that in reality, passing in an opening token will only ever
return a single element array, and so
+ * will passing in a closing token that is not TokenEND, but this
method is designed to work generically
+ * anyway, so that any addition to the BRACKET_TOKENS arry will be
handled properly.
+ *
+ * @param token the token whose pair is sought
+ * @return the set of tokens that can pair with the given token
+ */
+ public static int[] getMatchingTokens(int token) {
+ List list = (List)fMatchingPeers.get(Integer.valueOf(token));
+ int count = list.size();
+ int[] intArray = new int[count];
+ for (int i = 0; i < count; i++) {
+ intArray[i] = ((Integer)list.get(i)).intValue();
+ }
+ Arrays.sort(intArray);
+ return intArray;
+ }
+
/* character constants */
private static final char LBRACE= '{';
private static final char RBRACE= '}';
@@ -53,7 +186,7 @@
private static final char RBRACKET= ']';
private static final char QUESTIONMARK= '?';
private static final char EQUAL= '=';
-
+
/**
* Specifies the stop condition, upon which the <code>scanXXX</code>
methods will decide whether
* to keep scanning or not. This interface may implemented by clients.
@@ -97,6 +230,21 @@
}
/**
+ * Stops upon a non-whitespace character but ignores end-of-line
backslashes
+ *
+ * @see NonWhitespace
+ */
+ private class NonWhitespaceSkipLineContinuationsDefaultPartition
extends NonWhitespaceDefaultPartition {
+ /*
+ * @see
org.eclipse.jdt.internal.ui.text.RubyHeuristicScanner.StopCondition#stop(char)
+ */
+ public boolean stop(char ch, int position, boolean forward) {
+ return super.stop(ch, position, true) &&
+ !(ch == '\\' && position ==
getLineEndOfOffset(position));
+ }
+ }
+
+ /**
* Stops upon a non-java identifier (as defined by [EMAIL PROTECTED]
Character#isRubyIdentifierPart(char)}) character.
*/
private static class NonRubyIdentifierPart implements StopCondition {
@@ -121,6 +269,7 @@
return super.stop(ch, position, true) ||
!isDefaultPartition(position);
}
}
+
/**
* Stops upon a character in the default partition that matches the
given character list.
@@ -238,6 +387,124 @@
}
+ /**
+ * Finds the token that closes the block starting at the given
openingToken.
+ * E.g., given a '{', it will return the next '}'; given an 'if' it
will return
+ * the next 'end' (unless the 'if' is a conditional modifier, having no
'end')
+ *
+ * The method is careful to track scope so that an 'end' which closes
any other
+ * blocks along the way won't be returned.
+ *
+ * (This is actually tricky in Ruby because there are so many different
ways to
+ * start a block that finishes with 'end')
+ *
+ * @param start the offset to start searching at
+ * @param openingToken the token that opens the block
+ * @return the offset of the last char of the closing token or NOT_FOUND
+ * @throws BadLocationException
+ */
+ public int findClosingPeerToken(int start, final int openingToken)
throws BadLocationException {
+ // there's only one closing token for this opening token, but
there may be many
+ // other opening tokens for that closing token.
+ // E.g. DEF is closed by END, but the next END may be closing a
BEGIN that was started in the meantime
+
+ int closingToken = getMatchingTokens(openingToken)[0];
+ int[] openers = getMatchingTokens(closingToken);
+
+ int depth= 1;
+
+ int token = tokenAt(start);
+ token = nextToken(getPosition(), UNBOUND);
+ start = getPosition();
+
+ while (true) {
+ token = nextToken(start, UNBOUND);
+ start = getPosition(); // updated by nextToken
+ if (token == TokenEOF) {
+ return NOT_FOUND;
+ }
+ else if (token == closingToken) {
+ // close off this scope
+ depth--;
+ if (depth == 0) {
+ // we're back at the outermost scope,
so we're done
+ return start - 1;
+ }
+ }
+ else if (Arrays.binarySearch(openers, token) >= 0) {
+ // found another opener with the same end, open
another scope
+ if (!isConditionalModifier(token, start, true))
{
+ // ignore it if it's a modifier like
"puts x if (y);".
+ // So a standalone if or unless without
an end doesnt open a new scope
+ depth++;
+ }
+ }
+ // else token is neither a an opener or closer, loop to
the next one
+ }
+ }
+
+ /**
+ * Finds the token, searching backward, that openss the block ending
at the given closingToken.
+ * E.g., given a '}', it will return the next '{'; given an 'end' it
will return
+ * the previous 'if' (skipping any 'if's that are conditional
modifiers, having no 'end')
+ *
+ * The method is careful to track scope so that an 'end' which closes
any other
+ * blocks along the way won't be returned.
+ *
+ * (This is actually tricky in Ruby because there are so many different
ways to
+ * start a block that finishes with 'end')
+ *
+ * @param start the offset to start searching at
+ * @param closingToken the token that closes the block
+ * @return the offset of the last char of the opening token or NOT_FOUND
+ * @throws BadLocationException
+ */
+ public int findOpeningPeerToken(int start, final int closingToken)
throws BadLocationException {
+ // there could be a number of tokens that would open this
closing token, particularly
+ // if it's a ruby "end" keyword, so we have look out other
scopes being opened on the way
+ // to one of them.
+ // But for all those other potential openers, this is the only
closer.
+ // If we have an END we might find a BEGIN to open it, butif we
find another END before that BEGIN
+ // we open another scope with the END, close it with the BEGIN
and keep searching.
+
+ int[] openers = getMatchingTokens(closingToken);
+
+ int depth= 1;
+ int token = tokenAt(start);
+ token = previousToken(getPosition(), UNBOUND); // skip back to
before the closing token
+ start = getPosition();
+
+ while (true) {
+
+ token = previousToken(start - 1, UNBOUND);
+ start = getPosition(); // updated by nextToken to the
RHS of the found token
+
+ if (token == TokenEOF) {
+ return NOT_FOUND;
+ }
+ else if (token == closingToken) {
+ // found another opener with the same end, open
another scope
+ depth++;
+
+ }
+ else if (Arrays.binarySearch(openers, token) >= 0) {
+ // Found an opener that matches our closer,
close off this scope
+ //
+ if (!isConditionalModifier(token, start,
false)) {
+ // ignore it if it's a modifier like
"puts x if (y);".
+ // So a standalone if or unless without
an end doesnt close a scope
+ depth--;
+ if (depth == 0) {
+ // we're back at the outermost
scope, so we're done
+ return start;
+ }
+ }
+
+ }
+ // else token is neither a an opener or closer, loop to
the next one
+ }
+ }
+
/** The document being scanned. */
private IDocument fDocument;
/** The partitioning being used for scanning. */
@@ -254,6 +521,7 @@
/* preset stop conditions */
private final StopCondition fNonWSDefaultPart= new
NonWhitespaceDefaultPartition();
+ private final StopCondition fNonWSContinuationDefaultPart= new
NonWhitespaceSkipLineContinuationsDefaultPartition();
private final static StopCondition fNonWS= new NonWhitespace();
private final StopCondition fNonIdent= new
NonRubyIdentifierPartDefaultPartition();
@@ -336,10 +604,12 @@
// assume an ident or keyword
int from= pos, to;
pos= scanForward(pos + 1, bound, fNonIdent);
- if (pos == NOT_FOUND)
+ if (pos == NOT_FOUND) {
to= bound == UNBOUND ? fDocument.getLength() :
bound;
- else
+ }
+ else {
to= pos;
+ }
String identOrKeyword;
try {
@@ -357,6 +627,86 @@
}
/**
+ * Returns the token at the current position, starting just at the left
of it.
+ * The return value is one of the constants defined in [EMAIL
PROTECTED] Symbols}.
+ * After a call, [EMAIL PROTECTED] #getPosition()} will return the
position just before the scanned token
+ * starts (i.e. the next position that will be scanned).
+ *
+ * @param start the first character position in the document to consider
+ * @param bound the first position not to consider any more
+ * @return a constant from [EMAIL PROTECTED] Symbols} describing the
previous token
+ * @throws BadLocationException
+ */
+ public int tokenAt(int start) throws BadLocationException {
+ if (start < 0 || start >= fDocument.getLength()) throw new
BadLocationException();
+
+ // find the char under or to the left and only if it's nonWS,
then we scan back the start of the token
+ // the trick here is to use the very close bound: start - 1
+ int pos= scanBackward(start, start - 1, fNonWSDefaultPart);
+
+ int token;
+ if (pos == NOT_FOUND) // whitespace so no token
+ return TokenEOF; // NO TOKEN here
+
+ // we're right over a token, so the prevToken will be the
current one
+// fPos--;
+
+ switch (fChar) {
+ case LBRACE:
+ return TokenLBRACE;
+ case RBRACE:
+ return TokenRBRACE;
+ case LBRACKET:
+ return TokenLBRACKET;
+ case RBRACKET:
+ return TokenRBRACKET;
+ case LPAREN:
+ return TokenLPAREN;
+ case RPAREN:
+ return TokenRPAREN;
+ case SEMICOLON:
+ return TokenSEMICOLON;
+ case COLON:
+ return TokenCOLON;
+ case COMMA:
+ return TokenCOMMA;
+ case QUESTIONMARK:
+ return TokenQUESTIONMARK;
+ case EQUAL:
+ return TokenEQUAL;
+ }
+
+ // else
+ // FIXME Change calls to isJavaIdentifierPart to a custom
method that does isRubyidentifierPart
+ if (Character.isJavaIdentifierPart(fChar)) {
+ // assume an ident or keyword
+ int from, to= pos + 1;
+ pos= scanBackward(pos - 1, UNBOUND, fNonIdent);
+ if (pos == NOT_FOUND) {
+ from= 0;
+ } else {
+ from= pos + 1;
+ // get the whole ident or keyword, just in case
we started in the middle
+ to = pos= scanForward(from, UNBOUND, fNonIdent);
+ if (to == NOT_FOUND)
+ to= pos + 1;
+ }
+
+ String identOrKeyword;
+ try {
+ identOrKeyword= fDocument.get(from, to - from);
+ } catch (BadLocationException e) {
+ return TokenEOF;
+ }
+
+ return getToken(identOrKeyword);
+
+ }
+ // operators, number literals etc
+ return TokenOTHER;
+ }
+
+ /**
* Returns the next token in backward direction, starting at
<code>start</code>, and not extending
* further than <code>bound</code>. The return value is one of the
constants defined in [EMAIL PROTECTED] Symbols}.
* After a call, [EMAIL PROTECTED] #getPosition()} will return the
position just before the scanned token
@@ -370,8 +720,6 @@
int pos= scanBackward(start, bound, fNonWSDefaultPart);
if (pos == NOT_FOUND)
return TokenEOF;
-
- fPos--;
switch (fChar) {
case LBRACE:
@@ -404,11 +752,14 @@
// assume an ident or keyword
int from, to= pos + 1;
pos= scanBackward(pos - 1, bound, fNonIdent);
- if (pos == NOT_FOUND)
+ if (pos == NOT_FOUND) {
from= bound == UNBOUND ? 0 : bound + 1;
- else
+ } else {
from= pos + 1;
+ fPos = from;
+ }
+
String identOrKeyword;
try {
identOrKeyword= fDocument.get(from, to - from);
@@ -416,6 +767,7 @@
return TokenEOF;
}
+
return getToken(identOrKeyword);
}
@@ -442,6 +794,10 @@
case 3:
if ("for".equals(s)) //$NON-NLS-1$
return TokenFOR;
+ if ("def".equals(s)) //$NON-NLS-1$
+ return TokenDEF;
+ if ("end".equals(s)) //$NON-NLS-1$
+ return TokenEND;
break;
case 4:
if ("case".equals(s)) //$NON-NLS-1$
@@ -456,6 +812,8 @@
return TokenCLASS;
if ("while".equals(s)) //$NON-NLS-1$
return TokenWHILE;
+ if ("begin".equals(s))
+ return TokenBEGIN; //$NON-NLS-1$
break;
case 6:
if ("return".equals(s)) //$NON-NLS-1$
@@ -466,6 +824,8 @@
return TokenMODULE;
if ("unless".equals(s)) //$NON-NLS-1$
return TokenUNLESS;
+ if ("rescue".equals(s)) //$NON-NLS-1$
+ return TokenRESCUE;
break;
}
return TokenIDENT;
@@ -743,42 +1103,216 @@
return false;
}
+
/**
- * Checks if the line seems to be an open condition not followed by a
block (i.e. an if, while,
- * or for statement with just one following statement, see example
below).
- *
+ * Checks if the given token is a conditional modifier IF or UNLESS as
opposed to the start of a block.
+ * In ruby, normal IFs and UNLESSes always need an END to close the
block, even if their on one line,
+ * but modifiers are a special case where the conditional is at the end
of the line.
+ * E.g.
* <pre>
- * if (condition)
- * doStuff();
+ * puts "foo" if (x > 1);
* </pre>
+ * In this code there is no END for the IF. In all other cases there
must be an END.
*
- * <p>Algorithm: if the last non-WS, non-Comment code on the line is an
if (condition), while (condition),
- * for( expression), do, else, and there is no statement after that
</p>
+ * This method scans backward from the offset, which should be just
before
+ * the IF or UNLESS, until it finds the first non-whitespace or the
start of the line.
+ * If the first non-whitespace is a semicolon or the actual line start,
it will return true.
*
- * @param position the insert position of the new character
- * @param bound the lowest position to consider
- * @return <code>true</code> if the code is a conditional statement or
loop without a block, <code>false</code> otherwise
+ * It also handles line continuations where a line ends with a
backslash: imagine these cases
+ *
+ * <pre>
+ * begin
+ * puts "dog"
+ * if (false); end
+ * </pre>
+ * and
+ * <pre>
+ * begin
+ * puts "dog" \
+ * if (false); end
+ * </pre>
+ *
+ * The only difference is the backslash after dog (must be at the end
of the line with no ws after it)
+ * In the first case the END is a match for the IF, but in the second
case the IF is a conditional
+ * modifier on the "puts 'dog'" statement, because the previous line is
continued, so the END matches
+ * the BEGIN higher up.
+ *
+ * If the token is not an IF or UNLESS, then false is returned.
+ *
+ * @param token the token that is to be tested as a conditional
modifier (if or unless)
+ * @param offset the position anticipated to be the first text in the
line - we scan back from there
+ * @param forward true if we were searching forward implying the the
offset is at the end of the token
+ *
+ * @return <code>true</code> if there is no text between the offset and
the start of the line or a semicolon
*/
- public boolean isBracelessBlockStart(int position, int bound) {
- if (position < 1)
- return false;
-
- switch (previousToken(position, bound)) {
- case TokenDO:
- case TokenELSE:
- return true;
- case TokenRPAREN:
- position= findOpeningPeer(fPos, LPAREN, RPAREN);
- if (position > 0) {
- switch (previousToken(position - 1,
bound)) {
- case TokenIF:
- case TokenFOR:
- case TokenWHILE:
- return true;
- }
- }
+ public boolean isConditionalModifier(int token, int offset, boolean
forward)
+ {
+ if (token != TokenIF && token != TokenUNLESS) return false; //
not a conditional
+ int safeOffset = offset;
+ if (forward) {
+ // scanning forward when the token was found, so the
offset was at the end of the token
+ // we need to find the start of it.
+ previousToken(offset, UNBOUND);
+ offset = getPosition();
}
- return false;
+ try {
+ int line = fDocument.getLineOfOffset(offset);
+ // lines can be continued with backslashes, so loop back to the
real line
+ // number of the start of this logical line
+ while (line > 0 && isContinuedLine(line - 1)) {
+ line--;
+ }
+
+ int startOfLine =
fDocument.getLineInformation(line).getOffset();
+
+ int firstTextInLine = scanBackward(offset - 1, startOfLine,
fNonWSContinuationDefaultPart);
+ if (firstTextInLine == NOT_FOUND) {
+ return false; // nothing on the line before the if/else
+ }
+
+ // if the first non-ws char is a semicolon then we're at the
start of the logical line and
+ // we haven't found anything else, so it's not a oconditional
modifier
+ return fDocument.getChar(firstTextInLine) != ';';
+ } catch (BadLocationException e) {
+ return false; // default to nothing special
+ }
+ finally {
+ // we don't actually want to change the position, we're just
peeking here
+ fPos = safeOffset;
+ }
}
-}
+
+ /**
+ * Returns the position of the end of the line containing the specified
offset.
+ * @param offset some position in a line
+ * @return the end position of that line or zero if the offset is not
valid
+ */
+ private int getLineEndOfOffset(int offset) {
+ IRegion lineInfo;
+ try {
+ lineInfo = fDocument.getLineInformationOfOffset(offset);
+ } catch (BadLocationException e) {
+ return 0;
+ }
+ return lineInfo.getOffset() + lineInfo.getLength() - 1;
+ }
+
+ /**
+ * Determines whether the specified line ends with a backslash
indicating
+ * that it is continued beyond the line break.
+ * Note that ruby permits no whitespace after the \, it must be the
last char on the line.
+ *
+ * @param line number of the line to test
+ * @return true if the line ends with a backslash (\)
+ */
+ private boolean isContinuedLine(int line)
+ {
+ IRegion lineInfo;
+ try {
+ lineInfo = fDocument.getLineInformation(line);
+ int lineEnd = lineInfo.getOffset() +
lineInfo.getLength() - 1;
+ char ch = fDocument.getChar(lineEnd);
+ return fDocument.getChar(lineEnd) == '\\' &&
isDefaultPartition(lineEnd);
+ } catch (BadLocationException e) {
+ return false;
+ }
+ }
+
+
+ // debug methods commented out
+//
+// // debug method returns a description of the location
+// private String _locationToString(int pos)
+// {
+// StringBuffer buf = new StringBuffer(100);
+// buf.append(pos + ": ");
+// try {
+// buf.append(fDocument.get(pos - 10, 10));
+// buf.append("^");
+// buf.append(fDocument.get(pos, 10));
+// } catch (BadLocationException e) {
+// buf.append("BAD LOCATION");
+// }
+// return buf.toString();
+// }
+//
+// // debug method returns the name of a token and its id
+// static String _tokenToString(int token)
+// {
+// return _getTokenText(token) + " ("+token+")";
+// }
+//
+// // debug method returns the name of a token
+// static String _getTokenText(int token)
+// {
+// switch (token) {
+// case TokenEOF:
+// return "eof";
+// case TokenLBRACE:
+// return "{";
+// case TokenRBRACE:
+// return "}";
+// case TokenLBRACKET:
+// return "[";
+// case TokenRBRACKET:
+// return "]";
+// case TokenLPAREN:
+// return "(";
+// case TokenRPAREN:
+// return ")";
+// case TokenSEMICOLON:
+// return ";";
+// case TokenOTHER:
+// return "OTHER";
+// case TokenCOLON:
+// return ":";
+// case TokenQUESTIONMARK:
+// return "?";
+// case TokenCOMMA:
+// return ",";
+// case TokenEQUAL:
+// return "=";
+// case TokenLESSTHAN:
+// return "<";
+// case TokenGREATERTHAN:
+// return ">";
+// case TokenIF:
+// return "if";
+// case TokenDO:
+// return "do";
+// case TokenFOR:
+// return "for";
+// case TokenBEGIN:
+// return "begin";
+// case TokenEND:
+// return "end";
+// case TokenCASE:
+// return "case";
+// case TokenELSE:
+// return "else";
+// case TokenBREAK:
+// return "break";
+// case TokenRESCUE:
+// return "rescue";
+// case TokenWHILE:
+// return "while";
+// case TokenRETURN:
+// return "return";
+// case TokenSWITCH:
+// return "switch";
+// case TokenUNLESS:
+// return "unless";
+// case TokenCLASS:
+// return "class";
+// case TokenMODULE:
+// return "module";
+// case TokenIDENT:
+// return "ident";
+// case TokenDEF:
+// return "def";
+// default:
+// return "UNKNOWN";
+// }
+// }
+}
\ No newline at end of file
Index:
C:/ruby/rdtworkspace/org.rubypeople.rdt.ui/src/org/rubypeople/rdt/internal/ui/text/Symbols.java
===================================================================
---
C:/ruby/rdtworkspace/org.rubypeople.rdt.ui/src/org/rubypeople/rdt/internal/ui/text/Symbols.java
(revision 1938)
+++
C:/ruby/rdtworkspace/org.rubypeople.rdt.ui/src/org/rubypeople/rdt/internal/ui/text/Symbols.java
(working copy)
@@ -16,7 +16,7 @@
* @since 3.0
*/
public interface Symbols {
- // FIXME Add More Ruby keywords!
+ // FIXME Add More Ruby keywords! (Hint look in DefaultRubyParser)
int TokenEOF= -1;
int TokenLBRACE= 1;
int TokenRBRACE= 2;
@@ -48,4 +48,5 @@
int TokenCLASS= 1026;
int TokenMODULE= 1027;
int TokenIDENT= 2000;
+ int TokenDEF=2001; // TODO what number should this be?
}
-------------------------------------------------------------------------
Take Surveys. Earn Cash. Influence the Future of IT
Join SourceForge.net's Techsay panel and you'll get the chance to share your
opinions on IT & business topics through brief surveys-and earn cash
http://www.techsay.com/default.php?page=join.php&p=sourceforge&CID=DEVDEV
_______________________________________________
Rubyeclipse-development mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/rubyeclipse-development