ada.editor/src/org/netbeans/modules/ada/editor/lexer/AdaLexUtilities.java
author Andrea Lucarelli <raster@netbeans.org>
Sun, 22 Aug 2010 23:37:11 +0200
branchrelease68
changeset 16367 d2820c029d3a
parent 15779 367c7fdb5d23
permissions -rw-r--r--
Add JVM compiler support.
     1 /*
     2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     3  *
     4  * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
     5  *
     6  * The contents of this file are subject to the terms of either the GNU
     7  * General Public License Version 2 only ("GPL") or the Common
     8  * Development and Distribution License("CDDL") (collectively, the
     9  * "License"). You may not use this file except in compliance with the
    10  * License. You can obtain a copy of the License at
    11  * http://www.netbeans.org/cddl-gplv2.html
    12  * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    13  * specific language governing permissions and limitations under the
    14  * License.  When distributing the software, include this License Header
    15  * Notice in each file and include the License file at
    16  * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
    17  * particular file as subject to the "Classpath" exception as provided
    18  * by Sun in the GPL Version 2 section of the License file that
    19  * accompanied this code. If applicable, add the following below the
    20  * License Header, with the fields enclosed by brackets [] replaced by
    21  * your own identifying information:
    22  * "Portions Copyrighted [year] [name of copyright owner]"
    23  *
    24  * If you wish your version of this file to be governed by only the CDDL
    25  * or only the GPL Version 2, indicate your decision by adding
    26  * "[Contributor] elects to include this software in this distribution
    27  * under the [CDDL or GPL Version 2] license." If you do not indicate a
    28  * single choice of license, a recipient has the option to distribute
    29  * your version of this file under either the CDDL, the GPL Version 2 or
    30  * to extend the choice of license to its licensees as provided above.
    31  * However, if you add GPL Version 2 code and therefore, elected the GPL
    32  * Version 2 license, then the option applies only if the new code is
    33  * made subject to such option by the copyright holder.
    34  *
    35  * Contributor(s):
    36  *
    37  * Portions Copyrighted 2008 Sun Microsystems, Inc.
    38  */
    39 package org.netbeans.modules.ada.editor.lexer;
    40 
    41 import java.util.HashSet;
    42 import java.util.Set;
    43 
    44 import javax.swing.text.BadLocationException;
    45 import javax.swing.text.Document;
    46 import org.netbeans.api.lexer.Token;
    47 import org.netbeans.api.lexer.TokenHierarchy;
    48 import org.netbeans.api.lexer.TokenId;
    49 import org.netbeans.api.lexer.TokenSequence;
    50 import org.netbeans.editor.BaseDocument;
    51 import org.netbeans.editor.Utilities;
    52 import org.netbeans.modules.ada.editor.AdaMimeResolver;
    53 import org.netbeans.modules.csl.api.OffsetRange;
    54 import org.netbeans.modules.parsing.spi.Parser;
    55 import org.openide.filesystems.FileUtil;
    56 import org.openide.loaders.DataObject;
    57 import org.openide.util.Exceptions;
    58 
    59 
    60 /**
    61  *
    62  * Based on org.netbeans.modules.ruby.lexer.LexUtilities (Tor Norbye)
    63  *
    64  * Utilities associated with lexing or analyzing the document at the
    65  * lexical level, unlike AstUtilities which is contains utilities
    66  * to analyze parsed information about a document.
    67  *
    68  * @author Andrea Lucarelli
    69  */
    70 public class AdaLexUtilities {
    71     /**
    72      * Tokens that match a corresponding END statement. Even though while, unless etc.
    73      * can be statement modifiers, those luckily have different token ids so are not a problem
    74      * here.
    75      */
    76     private static final Set<TokenId> END_PAIRS = new HashSet<TokenId>();
    77 
    78     /**
    79      * Tokens that should cause indentation of the next line. This is true for all {@link #END_PAIRS},
    80      * but also includes tokens like "else" that are not themselves matched with end but also contribute
    81      * structure for indentation.
    82      *
    83      */
    84     private static final Set<TokenId> INDENT_WORDS = new HashSet<TokenId>();
    85 
    86     static {
    87         END_PAIRS.add(AdaTokenId.PROCEDURE);
    88         END_PAIRS.add(AdaTokenId.FUNCTION);
    89         END_PAIRS.add(AdaTokenId.DECLARE);
    90         END_PAIRS.add(AdaTokenId.FOR);
    91         END_PAIRS.add(AdaTokenId.WHILE);
    92         END_PAIRS.add(AdaTokenId.IF);
    93         END_PAIRS.add(AdaTokenId.PACKAGE);
    94         END_PAIRS.add(AdaTokenId.CASE);
    95         END_PAIRS.add(AdaTokenId.LOOP);
    96 
    97         INDENT_WORDS.addAll(END_PAIRS);
    98         // Add words that are not matched themselves with an "end",
    99         // but which also provide block structure to indented content
   100         // (usually part of a multi-keyword structure such as if-then-elsif-else-end if
   101         // where only the "if" is considered an end-pair.)
   102         INDENT_WORDS.add(AdaTokenId.ELSE);
   103         INDENT_WORDS.add(AdaTokenId.ELSIF);
   104         INDENT_WORDS.add(AdaTokenId.BEGIN);
   105     }
   106 
   107     private AdaLexUtilities() {
   108     }
   109 
   110     /**
   111      * For a possibly generated offset in an AST, return the corresponding lexing/true document offset
   112      */
   113     public static int getLexerOffset(Parser.Result result, int astOffset) {
   114         return result.getSnapshot().getOriginalOffset(astOffset);
   115     }
   116 
   117     public static OffsetRange getLexerOffsets(Parser.Result result, OffsetRange astRange) {
   118         int rangeStart = astRange.getStart();
   119         int start = result.getSnapshot().getOriginalOffset(rangeStart);
   120         if (start == rangeStart) {
   121             return astRange;
   122         } else if (start == -1) {
   123             return OffsetRange.NONE;
   124         } else {
   125             // Assumes the translated range maintains size
   126             return new OffsetRange(start, start + astRange.getLength());
   127         }
   128     }
   129 
   130     /**
   131      * Find the Ada token sequence (in case it's embedded in something else at the top level
   132      */
   133     @SuppressWarnings("unchecked")
   134     public static TokenSequence<?extends AdaTokenId> getAdaTokenSequence(BaseDocument doc, int offset) {
   135         TokenHierarchy<Document> th = TokenHierarchy.get((Document)doc);
   136         return getAdaTokenSequence(th, offset);
   137     }
   138 
   139     @SuppressWarnings("unchecked")
   140     public static TokenSequence<?extends AdaTokenId> getAdaTokenSequence(TokenHierarchy<Document> th, int offset) {
   141         TokenSequence<?extends AdaTokenId> ts = th.tokenSequence(AdaTokenId.language());
   142         return ts;
   143     }
   144 
   145     public static TokenSequence<?extends AdaTokenId> getPositionedSequence(BaseDocument doc, int offset) {
   146         TokenSequence<?extends AdaTokenId> ts = getAdaTokenSequence(doc, offset);
   147 
   148         if (ts != null) {
   149             try {
   150                 ts.move(offset);
   151             } catch (AssertionError e) {
   152                 DataObject dobj = (DataObject)doc.getProperty(Document.StreamDescriptionProperty);
   153 
   154                 if (dobj != null) {
   155                     Exceptions.attachMessage(e, FileUtil.getFileDisplayName(dobj.getPrimaryFile()));
   156                 }
   157 
   158                 throw e;
   159             }
   160 
   161             if (!ts.moveNext() && !ts.movePrevious()) {
   162                 return null;
   163             }
   164 
   165             return ts;
   166         }
   167 
   168         return null;
   169     }
   170 
   171 
   172     public static Token<?extends AdaTokenId> getToken(BaseDocument doc, int offset) {
   173         TokenSequence<?extends AdaTokenId> ts = getPositionedSequence(doc, offset);
   174 
   175         if (ts != null) {
   176             return ts.token();
   177         }
   178 
   179         return null;
   180     }
   181 
   182     public static char getTokenChar(BaseDocument doc, int offset) {
   183         Token<?extends AdaTokenId> token = getToken(doc, offset);
   184 
   185         if (token != null) {
   186             String text = token.text().toString();
   187 
   188             if (text.length() > 0) { // Usually true, but I could have gotten EOF right?
   189 
   190                 return text.charAt(0);
   191             }
   192         }
   193 
   194         return 0;
   195     }
   196 
   197     /** Search forwards in the token sequence until a token of type <code>down</code> is found */
   198     public static OffsetRange findHeredocEnd(TokenSequence<?extends AdaTokenId> ts,  Token<?extends AdaTokenId> startToken) {
   199 //        // Look for the end of the given heredoc
   200 //        String text = startToken.text().toString();
   201 //        assert text.startsWith("<<");
   202 //        text = text.substring(2);
   203 //        if (text.startsWith("-")) {
   204 //            text = text.substring(1);
   205 //        }
   206 //        if ((text.startsWith("\"") && text.endsWith("\"")) || (text.startsWith("'") && text.endsWith("'"))) {
   207 //            text = text.substring(0, text.length()-2);
   208 //        }
   209 //        String textn = text+"\n";
   210 //
   211 //        while (ts.moveNext()) {
   212 //            Token<?extends AdaTokenId> token = ts.token();
   213 //            TokenId id = token.id();
   214 //
   215 //            if (id == AdaTokenId.STRING_END || id == AdaTokenId.QUOTED_STRING_END) {
   216 //                String t = token.text().toString();
   217 //                if (text.equals(t) || textn.equals(t)) {
   218 //                    return new OffsetRange(ts.offset(), ts.offset() + token.length());
   219 //                }
   220 //            }
   221 //        }
   222 //
   223         return OffsetRange.NONE;
   224     }
   225 
   226     /** Search forwards in the token sequence until a token of type <code>down</code> is found */
   227     public static OffsetRange findHeredocBegin(TokenSequence<?extends AdaTokenId> ts,  Token<?extends AdaTokenId> endToken) {
   228 //        // Look for the end of the given heredoc
   229 //        String text = endToken.text().toString();
   230 //        if (text.endsWith("\n")) {
   231 //            text = text.substring(0, text.length()-1);
   232 //        }
   233 //        String textQuotes = "\"" + text + "\"";
   234 //        String textSQuotes = "'" + text + "'";
   235 //
   236 //        while (ts.movePrevious()) {
   237 //            Token<?extends AdaTokenId> token = ts.token();
   238 //            TokenId id = token.id();
   239 //
   240 //            if (id == AdaTokenId.STRING_BEGIN || id == AdaTokenId.QUOTED_STRING_BEGIN) {
   241 //                String t = token.text().toString();
   242 //                String marker = null;
   243 //                if (t.startsWith("<<-")) {
   244 //                    marker = t.substring(3);
   245 //                } else if (t.startsWith("<<")) {
   246 //                    marker = t.substring(2);
   247 //                }
   248 //                if (marker != null && (text.equals(marker) || textQuotes.equals(marker) || textSQuotes.equals(marker))) {
   249 //                    return new OffsetRange(ts.offset(), ts.offset() + token.length());
   250 //                }
   251 //            }
   252 //        }
   253 //
   254         return OffsetRange.NONE;
   255     }
   256 
   257     /**
   258      * Search forwards in the token sequence until a token of type <code>down</code> is found
   259      */
   260     public static OffsetRange findFwd(BaseDocument doc, TokenSequence<?extends AdaTokenId> ts, TokenId up,
   261         TokenId down) {
   262         int balance = 0;
   263 
   264         while (ts.moveNext()) {
   265             Token<?extends AdaTokenId> token = ts.token();
   266             TokenId id = token.id();
   267 
   268             if (id == up) {
   269                 balance++;
   270             } else if (id == down) {
   271                 if (balance == 0) {
   272                     return new OffsetRange(ts.offset(), ts.offset() + token.length());
   273                 }
   274 
   275                 balance--;
   276             }
   277         }
   278 
   279         return OffsetRange.NONE;
   280     }
   281 
   282     /**
   283      * Search backwards in the token sequence until a token of type <code>up</code> is found
   284      */
   285     public static OffsetRange findBwd(BaseDocument doc, TokenSequence<?extends AdaTokenId> ts, TokenId up,
   286         TokenId down) {
   287         int balance = 0;
   288 
   289         while (ts.movePrevious()) {
   290             Token<?extends AdaTokenId> token = ts.token();
   291             TokenId id = token.id();
   292 
   293             if (id == up) {
   294                 if (balance == 0) {
   295                     return new OffsetRange(ts.offset(), ts.offset() + token.length());
   296                 }
   297 
   298                 balance++;
   299             } else if (id == down) {
   300                 balance--;
   301             }
   302         }
   303 
   304         return OffsetRange.NONE;
   305     }
   306 
   307     /**
   308      * Find the token that begins a block terminated by "end". This is a token
   309      * in the END_PAIRS array. Walk backwards and find the corresponding token.
   310      * It does not use indentation for clues since this could be wrong and be
   311      * precisely the reason why the user is using pair matching to see what's wrong.
   312      */
   313     public static OffsetRange findBegin(BaseDocument doc, TokenSequence<?extends AdaTokenId> ts) {
   314         int balance = 0;
   315 
   316         while (ts.movePrevious()) {
   317             Token<?extends AdaTokenId> token = ts.token();
   318             TokenId id = token.id();
   319 
   320             if (isBeginToken(id, doc, ts)) {
   321                 // No matching dot for "do" used in conditionals etc.)) {
   322                 if (balance == 0) {
   323                     return new OffsetRange(ts.offset(), ts.offset() + token.length());
   324                 }
   325 
   326                 balance--;
   327             } else if (id == AdaTokenId.END || id == AdaTokenId.END_CASE || id == AdaTokenId.END_IF || id == AdaTokenId.END_LOOP) {
   328                 balance++;
   329             }
   330         }
   331 
   332         return OffsetRange.NONE;
   333     }
   334 
   335     public static OffsetRange findEnd(BaseDocument doc, TokenSequence<?extends AdaTokenId> ts) {
   336         int balance = 0;
   337 
   338         while (ts.moveNext()) {
   339             Token<?extends AdaTokenId> token = ts.token();
   340             TokenId id = token.id();
   341 
   342             if (isBeginToken(id, doc, ts)) {
   343                 balance--;
   344             } else if (id == AdaTokenId.END || id == AdaTokenId.END_CASE || id == AdaTokenId.END_IF || id == AdaTokenId.END_LOOP) {
   345                 if (balance == 0) {
   346                     return new OffsetRange(ts.offset(), ts.offset() + token.length());
   347                 }
   348 
   349                 balance++;
   350             }
   351         }
   352 
   353         return OffsetRange.NONE;
   354     }
   355 
   356     /**
   357      * Determine whether "loop" is an indent-token (e.g. matches an end) or if
   358      * it's simply a separator in while, for expressions)
   359      */
   360     public static boolean isEndmatchingLoop(BaseDocument doc, int offset) {
   361         // In the following case, loop is dominant:
   362         //     loop
   363         //        whatever
   364         //     end loop;
   365         //
   366         // However, not here:
   367         //     while true loop
   368         //        whatever
   369         //     end loop;
   370         //
   371         // In the second case, the end matches the while, but in the first case
   372         // the end matches the loop
   373 
   374         // Look at the first token of the current line
   375         try {
   376             int first = Utilities.getRowFirstNonWhite(doc, offset);
   377             if (first != -1) {
   378                 Token<? extends AdaTokenId> token = getToken(doc, first);
   379                 if (token != null) {
   380                     TokenId id = token.id();
   381                     if (id == AdaTokenId.WHILE || id == AdaTokenId.FOR) {
   382                         return false;
   383                     }
   384                 }
   385             }
   386         } catch (BadLocationException ble) {
   387             Exceptions.printStackTrace(ble);
   388         }
   389 
   390         return true;
   391     }
   392 
   393     /**
   394      * Return true iff the given token is a token that should be matched
   395      * with a corresponding "end" token, such as "begin", "package"
   396      * etc.
   397      */
   398     public static boolean isBeginToken(TokenId id, BaseDocument doc, int offset) {
   399         if (id == AdaTokenId.LOOP) {
   400             return isEndmatchingLoop(doc, offset);
   401         }
   402         return END_PAIRS.contains(id);
   403     }
   404 
   405     /**
   406      * Return true iff the given token is a token that should be matched
   407      * with a corresponding "end" token, such as "begin", "package"
   408      * etc.
   409      */
   410     public static boolean isBeginToken(TokenId id, BaseDocument doc, TokenSequence<?extends AdaTokenId> ts) {
   411         if (id == AdaTokenId.LOOP) {
   412             return isEndmatchingLoop(doc, ts.offset());
   413         }
   414         return END_PAIRS.contains(id);
   415     }
   416 
   417     /**
   418      * Return true iff the given token is a token that indents its content,
   419      * such as the various begin tokens as well as "else", "when", etc.
   420      */
   421     public static boolean isIndentToken(TokenId id) {
   422         return INDENT_WORDS.contains(id);
   423     }
   424 
   425     /** Compute the balance of begin/end tokens on the line.
   426      * @param doc the document
   427      * @param offset The offset somewhere on the line
   428      * @param upToOffset If true, only compute the line balance up to the given offset (inclusive),
   429      *   and if false compute the balance for the whole line
   430      */
   431     public static int getBeginEndLineBalance(BaseDocument doc, int offset, boolean upToOffset) {
   432         try {
   433             int begin = Utilities.getRowStart(doc, offset);
   434             int end = upToOffset ? offset : Utilities.getRowEnd(doc, offset);
   435 
   436             TokenSequence<?extends AdaTokenId> ts = AdaLexUtilities.getAdaTokenSequence(doc, begin);
   437             if (ts == null) {
   438                 return 0;
   439             }
   440 
   441             ts.move(begin);
   442 
   443             if (!ts.moveNext()) {
   444                 return 0;
   445             }
   446 
   447             int balance = 0;
   448 
   449             do {
   450                 Token<?extends AdaTokenId> token = ts.token();
   451                 TokenId id = token.id();
   452 
   453                 if (isBeginToken(id, doc, ts)) {
   454                     balance++;
   455                 } else if (id == AdaTokenId.END || id == AdaTokenId.END_CASE || id == AdaTokenId.END_IF || id == AdaTokenId.END_LOOP) {
   456                     balance--;
   457                 }
   458             } while (ts.moveNext() && (ts.offset() <= end));
   459 
   460             return balance;
   461         } catch (BadLocationException ble) {
   462             Exceptions.printStackTrace(ble);
   463 
   464             return 0;
   465         }
   466     }
   467 
   468     /** Compute the balance of begin/end tokens on the line */
   469     public static int getLineBalance(BaseDocument doc, int offset, TokenId up, TokenId down) {
   470         try {
   471             int begin = Utilities.getRowStart(doc, offset);
   472             int end = Utilities.getRowEnd(doc, offset);
   473 
   474             TokenSequence<?extends AdaTokenId> ts = AdaLexUtilities.getAdaTokenSequence(doc, begin);
   475             if (ts == null) {
   476                 return 0;
   477             }
   478 
   479             ts.move(begin);
   480 
   481             if (!ts.moveNext()) {
   482                 return 0;
   483             }
   484 
   485             int balance = 0;
   486 
   487             do {
   488                 Token<?extends AdaTokenId> token = ts.token();
   489                 TokenId id = token.id();
   490 
   491                 if (id == up) {
   492                     balance++;
   493                 } else if (id == down) {
   494                     balance--;
   495                 }
   496             } while (ts.moveNext() && (ts.offset() <= end));
   497 
   498             return balance;
   499         } catch (BadLocationException ble) {
   500             Exceptions.printStackTrace(ble);
   501 
   502             return 0;
   503         }
   504     }
   505 
   506     /**
   507      * The same as braceBalance but generalized to any pair of matching
   508      * tokens.
   509      * @param open the token that increses the count
   510      * @param close the token that decreses the count
   511      */
   512     public static int getTokenBalance(BaseDocument doc, TokenId open, TokenId close, int offset)
   513         throws BadLocationException {
   514         TokenSequence<?extends AdaTokenId> ts = AdaLexUtilities.getAdaTokenSequence(doc, 0);
   515         if (ts == null) {
   516             return 0;
   517         }
   518 
   519         // XXX Why 0? Why not offset?
   520         ts.moveIndex(0);
   521 
   522         if (!ts.moveNext()) {
   523             return 0;
   524         }
   525 
   526         int balance = 0;
   527 
   528         do {
   529             Token t = ts.token();
   530 
   531             if (t.id() == open) {
   532                 balance++;
   533             } else if (t.id() == close) {
   534                 balance--;
   535             }
   536         } while (ts.moveNext());
   537 
   538         return balance;
   539     }
   540 
   541     /**
   542      * Return true iff the line for the given offset is a Ada comment line.
   543      * This will return false for lines that contain comments (even when the
   544      * offset is within the comment portion) but also contain code.
   545      */
   546     public static boolean isCommentOnlyLine(BaseDocument doc, int offset)
   547         throws BadLocationException {
   548         int begin = Utilities.getRowFirstNonWhite(doc, offset);
   549 
   550         if (begin == -1) {
   551             return false; // whitespace only
   552         }
   553 
   554         Token<? extends AdaTokenId> token = AdaLexUtilities.getToken(doc, begin);
   555         if (token != null) {
   556             return token.id() == AdaTokenId.COMMENT;
   557         }
   558 
   559         return false;
   560     }
   561 
   562     /**
   563      * Return the string at the given position, or null if none
   564      */
   565     @SuppressWarnings("unchecked")
   566     public static String getStringAt(int caretOffset, TokenHierarchy<Document> th) {
   567         TokenSequence<?extends AdaTokenId> ts = getAdaTokenSequence(th, caretOffset);
   568 
   569         if (ts == null) {
   570             return null;
   571         }
   572 
   573         ts.move(caretOffset);
   574 
   575         if (!ts.moveNext() && !ts.movePrevious()) {
   576             return null;
   577         }
   578 
   579         if (ts.offset() == caretOffset) {
   580             // We're looking at the offset to the RIGHT of the caret
   581             // and here I care about what's on the left
   582             if (!ts.movePrevious()) {
   583                 return null;
   584             }
   585         }
   586 
   587         Token<?extends AdaTokenId> token = ts.token();
   588 
   589         if (token != null) {
   590             TokenId id = token.id();
   591 
   592             String string = null;
   593 
   594             // Skip over embedded Ada segments and literal strings until you find the beginning
   595             int segments = 0;
   596 
   597             while ((id == AdaTokenId.UNKNOWN_TOKEN) || (id == AdaTokenId.STRING_LITERAL)) {
   598                 string = token.text().toString();
   599                 segments++;
   600                 if (!ts.movePrevious()) {
   601                     return null;
   602                 }
   603                 token = ts.token();
   604                 id = token.id();
   605             }
   606 
   607             if (id == AdaTokenId.STRING_LITERAL) {
   608                 if (segments == 1) {
   609                     return string;
   610                 } else {
   611                     // Build up the String from the sequence
   612                     StringBuilder sb = new StringBuilder();
   613 
   614                     while (ts.moveNext()) {
   615                         token = ts.token();
   616                         id = token.id();
   617 
   618                         if ((id == AdaTokenId.UNKNOWN_TOKEN) || (id == AdaTokenId.STRING_LITERAL)) {
   619                             sb.append(token.text());
   620                         } else {
   621                             break;
   622                         }
   623                     }
   624 
   625                     return sb.toString();
   626                 }
   627             }
   628         }
   629 
   630         return null;
   631     }
   632 
   633     /**
   634      * Check if the caret is inside a literal string that is associated with
   635      * a require statement.
   636      *
   637      * @return The offset of the beginning of the require string, or -1
   638      *     if the offset is not inside a require string.
   639      */
   640     public static int getRequireStringOffset(int caretOffset, TokenHierarchy<Document> th) {
   641         TokenSequence<?extends AdaTokenId> ts = getAdaTokenSequence(th, caretOffset);
   642 
   643         if (ts == null) {
   644             return -1;
   645         }
   646 
   647         ts.move(caretOffset);
   648 
   649         if (!ts.moveNext() && !ts.movePrevious()) {
   650             return -1;
   651         }
   652 
   653         if (ts.offset() == caretOffset) {
   654             // We're looking at the offset to the RIGHT of the caret
   655             // and here I care about what's on the left
   656             if (!ts.movePrevious()) {
   657                 return -1;
   658             }
   659         }
   660 
   661         Token<?extends AdaTokenId> token = ts.token();
   662 
   663         if (token != null) {
   664             TokenId id = token.id();
   665 
   666             // Skip over embedded Ada segments and literal strings until you find the beginning
   667             while ((id == AdaTokenId.UNKNOWN_TOKEN) || (id == AdaTokenId.STRING_LITERAL)) {
   668                 if (!ts.movePrevious()) {
   669                     return -1;
   670                 }
   671                 token = ts.token();
   672                 id = token.id();
   673             }
   674 
   675             int stringStart = ts.offset() + token.length();
   676 
   677             if (id == AdaTokenId.STRING_LITERAL) {
   678                 // Completion of literal strings within require calls
   679                 while (ts.movePrevious()) {
   680                     token = ts.token();
   681 
   682                     id = token.id();
   683 
   684                     if ((id == AdaTokenId.WHITESPACE) || (id == AdaTokenId.LPAREN) ||
   685                             (id == AdaTokenId.STRING_LITERAL)) {
   686                         continue;
   687                     }
   688 
   689                     if (id == AdaTokenId.IDENTIFIER) {
   690                         String text = token.text().toString();
   691 
   692                         if (text.equals("require") || text.equals("load")) {
   693                             return stringStart;
   694                         } else {
   695                             return -1;
   696                         }
   697                     } else {
   698                         return -1;
   699                     }
   700                 }
   701             }
   702         }
   703 
   704         return -1;
   705     }
   706 
   707     public static int getSingleQuotedStringOffset(int caretOffset, TokenHierarchy<Document> th) {
   708         return getLiteralStringOffset(caretOffset, th, AdaTokenId.STRING_LITERAL);
   709     }
   710 
   711     public static int getDoubleQuotedStringOffset(int caretOffset, TokenHierarchy<Document> th) {
   712         return getLiteralStringOffset(caretOffset, th, AdaTokenId.STRING_LITERAL);
   713     }
   714 
   715     /**
   716      * Determine if the caret is inside a literal string, and if so, return its starting
   717      * offset. Return -1 otherwise.
   718      */
   719     @SuppressWarnings("unchecked")
   720     private static int getLiteralStringOffset(int caretOffset, TokenHierarchy<Document> th,
   721         AdaTokenId begin) {
   722         TokenSequence<?extends AdaTokenId> ts = getAdaTokenSequence(th, caretOffset);
   723 
   724         if (ts == null) {
   725             return -1;
   726         }
   727 
   728         ts.move(caretOffset);
   729 
   730         if (!ts.moveNext() && !ts.movePrevious()) {
   731             return -1;
   732         }
   733 
   734         if (ts.offset() == caretOffset) {
   735             // We're looking at the offset to the RIGHT of the caret
   736             // and here I care about what's on the left
   737             if (!ts.movePrevious()) {
   738                 return -1;
   739             }
   740         }
   741 
   742         Token<?extends AdaTokenId> token = ts.token();
   743 
   744         if (token != null) {
   745             TokenId id = token.id();
   746 
   747             // Skip over embedded Ada segments and literal strings until you find the beginning
   748             while ((id == AdaTokenId.UNKNOWN_TOKEN) || (id == AdaTokenId.STRING_LITERAL)) {
   749                 if (!ts.movePrevious()) {
   750                     return -1;
   751                 }
   752                 token = ts.token();
   753                 id = token.id();
   754             }
   755 
   756             if (id == begin) {
   757                 if (!ts.moveNext()) {
   758                     return -1;
   759                 }
   760 
   761                 return ts.offset();
   762             }
   763         }
   764 
   765         return -1;
   766     }
   767 
   768     public static boolean isInsideQuotedString(BaseDocument doc, int offset) {
   769         TokenSequence<?extends AdaTokenId> ts = AdaLexUtilities.getAdaTokenSequence(doc, offset);
   770 
   771         if (ts == null) {
   772             return false;
   773         }
   774 
   775         ts.move(offset);
   776 
   777         if (ts.moveNext()) {
   778             Token<?extends AdaTokenId> token = ts.token();
   779             TokenId id = token.id();
   780             if (id == AdaTokenId.STRING_LITERAL) {
   781                 return true;
   782             }
   783         }
   784         if (ts.movePrevious()) {
   785             Token<?extends AdaTokenId> token = ts.token();
   786             TokenId id = token.id();
   787             if (id == AdaTokenId.STRING_LITERAL) {
   788                 return true;
   789             }
   790         }
   791 
   792         return false;
   793     }
   794 
   795 
   796     public static OffsetRange getCommentBlock(BaseDocument doc, int caretOffset) {
   797         // Check if the caret is within a comment, and if so insert a new
   798         // leaf "node" which contains the comment line and then comment block
   799         try {
   800             Token<?extends AdaTokenId> token = AdaLexUtilities.getToken(doc, caretOffset);
   801 
   802             if ((token != null) && (token.id() == AdaTokenId.COMMENT)) {
   803                 // First add a range for the current line
   804                 int begin = Utilities.getRowStart(doc, caretOffset);
   805                 int end = Utilities.getRowEnd(doc, caretOffset);
   806 
   807                 if (AdaLexUtilities.isCommentOnlyLine(doc, caretOffset)) {
   808 
   809                     while (begin > 0) {
   810                         int newBegin = Utilities.getRowStart(doc, begin - 1);
   811 
   812                         if ((newBegin < 0) || !AdaLexUtilities.isCommentOnlyLine(doc, newBegin)) {
   813                             begin = Utilities.getRowFirstNonWhite(doc, begin);
   814                             break;
   815                         }
   816 
   817                         begin = newBegin;
   818                     }
   819 
   820                     int length = doc.getLength();
   821 
   822                     while (true) {
   823                         int newEnd = Utilities.getRowEnd(doc, end + 1);
   824 
   825                         if ((newEnd >= length) || !AdaLexUtilities.isCommentOnlyLine(doc, newEnd)) {
   826                             end = Utilities.getRowLastNonWhite(doc, end)+1;
   827                             break;
   828                         }
   829 
   830                         end = newEnd;
   831                     }
   832 
   833                     if (begin < end) {
   834                         return new OffsetRange(begin, end);
   835                     }
   836                 } else {
   837                     // It's just a line comment next to some code
   838                     TokenHierarchy<Document> th = TokenHierarchy.get((Document)doc);
   839                     int offset = token.offset(th);
   840                     return new OffsetRange(offset, offset + token.length());
   841                 }
   842             }
   843             //else if (token != null && token.id() == AdaTokenId.DOCUMENTATION) {
   844             //    // Select the whole token block
   845             //    TokenHierarchy<BaseDocument> th = TokenHierarchy.get(doc);
   846             //    int begin = token.offset(th);
   847             //    int end = begin + token.length();
   848             //    return new OffsetRange(begin, end);
   849             //}
   850         } catch (BadLocationException ble) {
   851             Exceptions.printStackTrace(ble);
   852         }
   853 
   854         return OffsetRange.NONE;
   855     }
   856 
   857     /**
   858      * Back up to the first space character prior to the given offset - as long as
   859      * it's on the same line!  If there's only leading whitespace on the line up
   860      * to the lex offset, return the offset itself
   861      */
   862     public static int findSpaceBegin(BaseDocument doc, int lexOffset) {
   863         TokenSequence ts = AdaLexUtilities.getAdaTokenSequence(doc, lexOffset);
   864         if (ts == null) {
   865             return lexOffset;
   866         }
   867         boolean allowPrevLine = false;
   868         int lineStart;
   869         try {
   870             lineStart = Utilities.getRowStart(doc, Math.min(lexOffset, doc.getLength()));
   871             int prevLast = lineStart-1;
   872             if (lineStart > 0) {
   873                 prevLast = Utilities.getRowLastNonWhite(doc, lineStart-1);
   874                 if (prevLast != -1) {
   875                     char c = doc.getText(prevLast, 1).charAt(0);
   876                     if (c == ',') {
   877                         // Arglist continuation? // TODO : check lexing
   878                         allowPrevLine = true;
   879                     }
   880                 }
   881             }
   882             if (!allowPrevLine) {
   883                 int firstNonWhite = Utilities.getRowFirstNonWhite(doc, lineStart);
   884                 if (lexOffset <= firstNonWhite || firstNonWhite == -1) {
   885                     return lexOffset;
   886                 }
   887             } else {
   888                 // Make lineStart so small that Math.max won't cause any problems
   889                 int firstNonWhite = Utilities.getRowFirstNonWhite(doc, lineStart);
   890                 if (prevLast >= 0 && (lexOffset <= firstNonWhite || firstNonWhite == -1)) {
   891                     return prevLast+1;
   892                 }
   893                 lineStart = 0;
   894             }
   895         } catch (BadLocationException ble) {
   896             Exceptions.printStackTrace(ble);
   897             return lexOffset;
   898         }
   899         ts.move(lexOffset);
   900         if (ts.moveNext()) {
   901             if (lexOffset > ts.offset()) {
   902                 // We're in the middle of a token
   903                 return Math.max((ts.token().id() == AdaTokenId.WHITESPACE) ?
   904                     ts.offset() : lexOffset, lineStart);
   905             }
   906             while (ts.movePrevious()) {
   907                 Token token = ts.token();
   908                 if (token.id() != AdaTokenId.WHITESPACE) {
   909                     return Math.max(ts.offset() + token.length(), lineStart);
   910                 }
   911             }
   912         }
   913 
   914         return lexOffset;
   915     }
   916 
   917     /**
   918      * Get the rdoc documentation associated with the given node in the given document.
   919      * The node must have position information that matches the source in the document.
   920      */
   921     public static OffsetRange findRDocRange(BaseDocument baseDoc, int methodBegin) {
   922         int begin = methodBegin;
   923         try {
   924             if (methodBegin >= baseDoc.getLength()) {
   925                 return OffsetRange.NONE;
   926             }
   927 
   928             // Search to previous lines, locate comments. Once we have a non-whitespace line that isn't
   929             // a comment, we're done
   930 
   931             int offset = Utilities.getRowStart(baseDoc, methodBegin);
   932             offset--;
   933 
   934             // Skip empty and whitespace lines
   935             while (offset >= 0) {
   936                 // Find beginning of line
   937                 offset = Utilities.getRowStart(baseDoc, offset);
   938 
   939                 if (!Utilities.isRowEmpty(baseDoc, offset) &&
   940                         !Utilities.isRowWhite(baseDoc, offset)) {
   941                     break;
   942                 }
   943 
   944                 offset--;
   945             }
   946 
   947             if (offset < 0) {
   948                 return OffsetRange.NONE;
   949             }
   950 
   951             while (offset >= 0) {
   952                 // Find beginning of line
   953                 offset = Utilities.getRowStart(baseDoc, offset);
   954 
   955                 if (Utilities.isRowEmpty(baseDoc, offset) || Utilities.isRowWhite(baseDoc, offset)) {
   956                     // Empty lines not allowed within an rdoc
   957                     break;
   958                 }
   959 
   960                 // This is a comment line we should include
   961                 int lineBegin = Utilities.getRowFirstNonWhite(baseDoc, offset);
   962                 int lineEnd = Utilities.getRowLastNonWhite(baseDoc, offset) + 1;
   963                 String line = baseDoc.getText(lineBegin, lineEnd - lineBegin);
   964 
   965                 // TODO: should be changed
   966 				// Tolerate "public", "private" and "protected" here --
   967                 // Test::Unit::Assertions likes to put these in front of each
   968                 // method.
   969                 if (line.startsWith("#")) {
   970                     begin = lineBegin;
   971                 } else if (line.startsWith("=end") &&
   972                         (lineBegin == Utilities.getRowStart(baseDoc, offset))) {
   973                     // It could be a =begin,=end document - see scanf.rb in Ada lib for example. Treat this differently.
   974                     int docBegin = findInlineDocStart(baseDoc, offset);
   975                     if (docBegin != -1) {
   976                         begin = docBegin;
   977                     } else {
   978                         return OffsetRange.NONE;
   979                     }
   980                 } else if (line.equals("public") || line.equals("private") ||
   981                         line.equals("protected")) { // NOI18N
   982                                                     // Skip newlines back up to the comment
   983                     offset--;
   984 
   985                     while (offset >= 0) {
   986                         // Find beginning of line
   987                         offset = Utilities.getRowStart(baseDoc, offset);
   988 
   989                         if (!Utilities.isRowEmpty(baseDoc, offset) &&
   990                                 !Utilities.isRowWhite(baseDoc, offset)) {
   991                             break;
   992                         }
   993 
   994                         offset--;
   995                     }
   996 
   997                     continue;
   998                 } else {
   999                     // No longer in a comment
  1000                     break;
  1001                 }
  1002 
  1003                 // Previous line
  1004                 offset--;
  1005             }
  1006         } catch (BadLocationException ble) {
  1007             Exceptions.printStackTrace(ble);
  1008         }
  1009 
  1010         if (methodBegin > begin) {
  1011             return new OffsetRange(begin, methodBegin);
  1012         } else {
  1013             return OffsetRange.NONE;
  1014         }
  1015     }
  1016 
  1017     private static int findInlineDocStart(BaseDocument baseDoc, int offset) throws BadLocationException {
  1018         // offset points to a line containing =end
  1019         // Skip the =end list
  1020         offset = Utilities.getRowStart(baseDoc, offset);
  1021         offset--;
  1022 
  1023         // Search backwards in the document for the =begin (if any) and add all lines in reverse
  1024         // order in between.
  1025         while (offset >= 0) {
  1026             // Find beginning of line
  1027             offset = Utilities.getRowStart(baseDoc, offset);
  1028 
  1029             // This is a comment line we should include
  1030             int lineBegin = offset;
  1031             int lineEnd = Utilities.getRowEnd(baseDoc, offset);
  1032             String line = baseDoc.getText(lineBegin, lineEnd - lineBegin);
  1033 
  1034             if (line.startsWith("=begin")) {
  1035                 // We're done!
  1036                 return lineBegin;
  1037             }
  1038 
  1039             // Previous line
  1040             offset--;
  1041         }
  1042 
  1043         return -1;
  1044     }
  1045 }