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
Rubyeclipse-development@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/rubyeclipse-development

Reply via email to