KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > ruby > lexer > LexUtilities


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19 package org.netbeans.modules.ruby.lexer;
20
21 import java.util.HashSet JavaDoc;
22 import java.util.Set JavaDoc;
23
24 import javax.swing.text.BadLocationException JavaDoc;
25 import javax.swing.text.Document JavaDoc;
26
27 import org.netbeans.api.gsf.GsfTokenId;
28 import org.netbeans.api.gsf.OffsetRange;
29 import org.netbeans.api.gsf.annotations.NonNull;
30 import org.netbeans.api.lexer.Token;
31 import org.netbeans.api.lexer.TokenHierarchy;
32 import org.netbeans.api.lexer.TokenId;
33 import org.netbeans.api.lexer.TokenSequence;
34 import org.netbeans.editor.BaseDocument;
35 import org.netbeans.editor.BaseDocument;
36 import org.netbeans.editor.Utilities;
37 import org.netbeans.modules.ruby.lexer.RubyTokenId;
38 import org.openide.util.Exceptions;
39
40
41 /**
42  * Utilities associated with lexing or analyzing the document at the
43  * lexical level, unlike AstUtilities which is contains utilities
44  * to analyze parsed information about a document.
45  *
46  * @author Tor Norbye
47  */

48 public class LexUtilities {
49     /** Tokens that match a corresponding END statement. Even though while, unless etc.
50      * can be statement modifiers, those luckily have different token ids so are not a problem
51      * here.
52      */

53     private static final Set JavaDoc<TokenId> END_PAIRS = new HashSet JavaDoc<TokenId>();
54
55     /**
56      * Tokens that should cause indentation of the next line. This is true for all {@link #END_PAIRS},
57      * but also includes tokens like "else" that are not themselves matched with end but also contribute
58      * structure for indentation.
59      *
60      */

61     private static final Set JavaDoc<TokenId> INDENT_WORDS = new HashSet JavaDoc<TokenId>();
62
63     static {
64         END_PAIRS.add(RubyTokenId.BEGIN);
65         END_PAIRS.add(RubyTokenId.FOR);
66         END_PAIRS.add(RubyTokenId.CLASS);
67         END_PAIRS.add(RubyTokenId.DEF);
68         END_PAIRS.add(RubyTokenId.DO);
69         END_PAIRS.add(RubyTokenId.WHILE);
70         END_PAIRS.add(RubyTokenId.IF);
71         END_PAIRS.add(RubyTokenId.CLASS);
72         END_PAIRS.add(RubyTokenId.MODULE);
73         END_PAIRS.add(RubyTokenId.CASE);
74         END_PAIRS.add(RubyTokenId.LOOP);
75         END_PAIRS.add(RubyTokenId.UNTIL);
76         END_PAIRS.add(RubyTokenId.UNLESS);
77
78         INDENT_WORDS.addAll(END_PAIRS);
79         // Add words that are not matched themselves with an "end",
80
// but which also provide block structure to indented content
81
// (usually part of a multi-keyword structure such as if-then-elsif-else-end
82
// where only the "if" is considered an end-pair.)
83
INDENT_WORDS.add(RubyTokenId.ELSE);
84         INDENT_WORDS.add(RubyTokenId.ELSIF);
85         INDENT_WORDS.add(RubyTokenId.ENSURE);
86         INDENT_WORDS.add(RubyTokenId.WHEN);
87         INDENT_WORDS.add(RubyTokenId.RESCUE);
88
89         // XXX What about BEGIN{} and END{} ?
90
}
91
92     private LexUtilities() {
93     }
94
95     public static TokenSequence<?extends GsfTokenId> getTokenSequence(BaseDocument doc) {
96         TokenHierarchy<Document JavaDoc> th = TokenHierarchy.get((Document JavaDoc)doc);
97
98         TokenSequence<?extends GsfTokenId> ts = th.tokenSequence(RubyTokenId.language());
99
100         return ts;
101     }
102
103     public static Token<?extends GsfTokenId> getToken(BaseDocument doc, int offset) {
104         TokenSequence<?extends GsfTokenId> ts = getTokenSequence(doc);
105
106         if (ts != null) {
107             ts.move(offset);
108
109             if (!ts.moveNext() && !ts.movePrevious()) {
110                 return null;
111             }
112
113             Token<?extends GsfTokenId> token = ts.token();
114
115             return token;
116         }
117
118         return null;
119     }
120
121     public static char getTokenChar(BaseDocument doc, int offset) {
122         Token<?extends GsfTokenId> token = getToken(doc, offset);
123
124         if (token != null) {
125             String JavaDoc text = token.text().toString();
126
127             if (text.length() > 0) { // Usually true, but I could have gotten EOF right?
128

129                 return text.charAt(0);
130             }
131         }
132
133         return 0;
134     }
135
136     /** Search forwards in the token sequence until a token of type <code>down</code> is found */
137     public static OffsetRange findFwd(TokenSequence<?extends GsfTokenId> ts, TokenId up,
138         TokenId down) {
139         int balance = 0;
140
141         while (ts.moveNext()) {
142             Token<?extends GsfTokenId> token = ts.token();
143
144             if (token.id() == up) {
145                 balance++;
146             } else if (token.id() == down) {
147                 if (balance == 0) {
148                     return new OffsetRange(ts.offset(), ts.offset() + token.length());
149                 }
150
151                 balance--;
152             }
153         }
154
155         return OffsetRange.NONE;
156     }
157
158     /** Search backwards in the token sequence until a token of type <code>up</code> is found */
159     public static OffsetRange findBwd(TokenSequence<?extends GsfTokenId> ts, TokenId up,
160         TokenId down) {
161         int balance = 0;
162
163         while (ts.movePrevious()) {
164             Token<?extends GsfTokenId> token = ts.token();
165
166             if (token.id() == up) {
167                 if (balance == 0) {
168                     return new OffsetRange(ts.offset(), ts.offset() + token.length());
169                 }
170
171                 balance++;
172             } else if (token.id() == down) {
173                 balance--;
174             }
175         }
176
177         return OffsetRange.NONE;
178     }
179
180     /** Find the token that begins a block terminated by "end". This is a token
181      * in the END_PAIRS array. Walk backwards and find the corresponding token.
182      * It does not use indentation for clues since this could be wrong and be
183      * precisely the reason why the user is using pair matching to see what's wrong.
184      */

185     public static OffsetRange findBegin(TokenSequence<?extends GsfTokenId> ts) {
186         int balance = 0;
187
188         while (ts.movePrevious()) {
189             Token<?extends GsfTokenId> token = ts.token();
190             TokenId id = token.id();
191
192             if (isBeginToken(id)) {
193                 if (balance == 0) {
194                     return new OffsetRange(ts.offset(), ts.offset() + token.length());
195                 }
196
197                 balance--;
198             } else if (id == RubyTokenId.END) {
199                 balance++;
200             }
201         }
202
203         return OffsetRange.NONE;
204     }
205
206     public static OffsetRange findEnd(TokenSequence<?extends GsfTokenId> ts) {
207         int balance = 0;
208
209         while (ts.moveNext()) {
210             Token<?extends GsfTokenId> token = ts.token();
211             TokenId id = token.id();
212
213             if (isBeginToken(id)) {
214                 balance--;
215             } else if (id == RubyTokenId.END) {
216                 if (balance == 0) {
217                     return new OffsetRange(ts.offset(), ts.offset() + token.length());
218                 }
219
220                 balance++;
221             }
222         }
223
224         return OffsetRange.NONE;
225     }
226
227     /**
228      * Return true iff the given token is a token that should be matched
229      * with a corresponding "end" token, such as "begin", "def", "module",
230      * etc.
231      */

232     public static boolean isBeginToken(TokenId id) {
233         return END_PAIRS.contains(id);
234     }
235
236     /**
237      * Return true iff the given token is a token that indents its content,
238      * such as the various begin tokens as well as "else", "when", etc.
239      */

240     public static boolean isIndentToken(TokenId id) {
241         return INDENT_WORDS.contains(id);
242     }
243
244     /** Compute the balance of begin/end tokens on the line */
245     public static int getBeginEndLineBalance(BaseDocument doc, int offset) {
246         TokenSequence<?extends GsfTokenId> ts = LexUtilities.getTokenSequence(doc);
247         assert ts != null;
248
249         try {
250             int begin = Utilities.getRowStart(doc, offset);
251             int end = Utilities.getRowEnd(doc, offset);
252
253             ts.move(begin);
254
255             if (!ts.moveNext()) {
256                 return 0;
257             }
258
259             int balance = 0;
260
261             do {
262                 Token<?extends GsfTokenId> token = ts.token();
263                 TokenId id = token.id();
264
265                 if (isBeginToken(id)) {
266                     balance++;
267                 } else if (id == RubyTokenId.END) {
268                     balance--;
269                 }
270             } while (ts.moveNext() && (ts.offset() <= end));
271
272             return balance;
273         } catch (BadLocationException JavaDoc ble) {
274             Exceptions.printStackTrace(ble);
275
276             return 0;
277         }
278     }
279
280     /** Compute the balance of begin/end tokens on the line */
281     public static int getLineBalance(BaseDocument doc, int offset, TokenId up, TokenId down) {
282         TokenSequence<?extends GsfTokenId> ts = LexUtilities.getTokenSequence(doc);
283         assert ts != null;
284
285         try {
286             int begin = Utilities.getRowStart(doc, offset);
287             int end = Utilities.getRowEnd(doc, offset);
288
289             ts.move(begin);
290
291             if (!ts.moveNext()) {
292                 return 0;
293             }
294
295             int balance = 0;
296
297             do {
298                 Token<?extends GsfTokenId> token = ts.token();
299                 TokenId id = token.id();
300
301                 if (id == up) {
302                     balance++;
303                 } else if (id == down) {
304                     balance--;
305                 }
306             } while (ts.moveNext() && (ts.offset() <= end));
307
308             return balance;
309         } catch (BadLocationException JavaDoc ble) {
310             Exceptions.printStackTrace(ble);
311
312             return 0;
313         }
314     }
315
316     /**
317      * The same as braceBalance but generalized to any pair of matching
318      * tokens.
319      * @param open the token that increses the count
320      * @param close the token that decreses the count
321      */

322     public static int getTokenBalance(BaseDocument doc, TokenId open, TokenId close)
323         throws BadLocationException JavaDoc {
324         TokenSequence<?extends GsfTokenId> ts = LexUtilities.getTokenSequence(doc);
325
326         ts.moveIndex(0);
327
328         if (!ts.moveNext()) {
329             return 0;
330         }
331
332         int balance = 0;
333
334         do {
335             Token t = ts.token();
336
337             if (t.id() == open) {
338                 balance++;
339             } else if (t.id() == close) {
340                 balance--;
341             }
342         } while (ts.moveNext());
343
344         return balance;
345     }
346
347     public static int getLineIndent(BaseDocument doc, int offset) {
348         try {
349             int start = Utilities.getRowStart(doc, offset);
350             int end;
351
352             if (Utilities.isRowWhite(doc, start)) {
353                 end = Utilities.getRowEnd(doc, offset);
354             } else {
355                 end = Utilities.getRowFirstNonWhite(doc, start);
356             }
357
358             int indent = end - start;
359
360             return indent;
361         } catch (BadLocationException JavaDoc ble) {
362             Exceptions.printStackTrace(ble);
363
364             return 0;
365         }
366     }
367
368     public static void indent(StringBuilder JavaDoc sb, int indent) {
369         for (int i = 0; i < indent; i++) {
370             sb.append(' ');
371         }
372     }
373
374     public static String JavaDoc getIndentString(int indent) {
375         StringBuilder JavaDoc sb = new StringBuilder JavaDoc(indent);
376         indent(sb, indent);
377
378         return sb.toString();
379     }
380
381     /**
382      * Return true iff the line for the given offset is a Ruby comment line.
383      * This will return false for lines that contain comments (even when the
384      * offset is within the comment portion) but also contain code.
385      */

386     public static boolean isCommentOnlyLine(BaseDocument doc, int offset)
387         throws BadLocationException JavaDoc {
388         int begin = Utilities.getRowFirstNonWhite(doc, offset);
389
390         if (begin == -1) {
391             return false; // whitespace only
392
}
393
394         if (begin == doc.getLength()) {
395             return false;
396         }
397
398         return doc.getText(begin, 1).equals("#");
399     }
400
401     public static void adjustLineIndentation(BaseDocument doc, int offset, int adjustment) {
402         try {
403             int lineBegin = Utilities.getRowStart(doc, offset);
404
405             if (adjustment > 0) {
406                 doc.remove(lineBegin, adjustment);
407             } else if (adjustment < 0) {
408                 doc.insertString(adjustment, LexUtilities.getIndentString(adjustment), null);
409             }
410         } catch (BadLocationException JavaDoc ble) {
411             Exceptions.printStackTrace(ble);
412         }
413     }
414
415     /** Adjust the indentation of the line containing the given offset to the provided
416      * indentation, and return the new indent.
417      */

418     public static int setLineIndentation(BaseDocument doc, int offset, int indent) {
419         int currentIndent = getLineIndent(doc, offset);
420
421         try {
422             int lineBegin = Utilities.getRowStart(doc, offset);
423
424             if (lineBegin == -1) {
425                 return currentIndent;
426             }
427
428             int adjust = currentIndent - indent;
429
430             if (adjust > 0) {
431                 // Make sure that we are only removing spaces here
432
String JavaDoc text = doc.getText(lineBegin, adjust);
433
434                 for (int i = 0; i < text.length(); i++) {
435                     if (!Character.isWhitespace(text.charAt(i))) {
436                         throw new RuntimeException JavaDoc(
437                             "Illegal indentation adjustment: Deleting non-whitespace chars: " +
438                             text);
439                     }
440                 }
441
442                 doc.remove(lineBegin, adjust);
443             } else if (adjust < 0) {
444                 adjust = -adjust;
445                 doc.insertString(lineBegin, getIndentString(adjust), null);
446             }
447
448             return indent;
449         } catch (BadLocationException JavaDoc ble) {
450             Exceptions.printStackTrace(ble);
451
452             return currentIndent;
453         }
454     }
455
456     /**
457      * Return the string at the given position, or null if none
458      */

459     @SuppressWarnings JavaDoc("unchecked")
460     public static String JavaDoc getStringAt(int caretOffset, TokenHierarchy<Document JavaDoc> th) {
461         TokenSequence<?extends GsfTokenId> ts = th.tokenSequence(RubyTokenId.language());
462
463         if (ts == null) {
464             return null;
465         }
466
467         ts.move(caretOffset);
468
469         if (!ts.moveNext() && !ts.movePrevious()) {
470             return null;
471         }
472
473         if (ts.offset() == caretOffset) {
474             // We're looking at the offset to the RIGHT of the caret
475
// and here I care about what's on the left
476
ts.movePrevious();
477         }
478
479         Token<?extends GsfTokenId> token = ts.token();
480
481         if (token != null) {
482             TokenId id = token.id();
483
484             // We're within a String that has embedded Ruby. Drop into the
485
// embedded language and see if we're within a literal string there.
486
if (id == RubyTokenId.EMBEDDED_RUBY) {
487                 ts = (TokenSequence)ts.embedded();
488                 assert ts != null;
489                 ts.move(caretOffset);
490
491                 if (!ts.moveNext() && !ts.movePrevious()) {
492                     return null;
493                 }
494
495                 token = ts.token();
496                 id = token.id();
497             }
498
499             String JavaDoc string = null;
500
501             // Skip over embedded Ruby segments and literal strings until you find the beginning
502
int segments = 0;
503
504             while ((id == RubyTokenId.ERROR) || (id == RubyTokenId.STRING_LITERAL) ||
505                     (id == RubyTokenId.QUOTED_STRING_LITERAL) || (id == RubyTokenId.EMBEDDED_RUBY)) {
506                 string = token.text().toString();
507                 segments++;
508                 ts.movePrevious();
509                 token = ts.token();
510                 id = token.id();
511             }
512
513             if ((id == RubyTokenId.STRING_BEGIN) || (id == RubyTokenId.QUOTED_STRING_BEGIN)) {
514                 if (segments == 1) {
515                     return string;
516                 } else {
517                     // Build up the String from the sequence
518
StringBuilder JavaDoc sb = new StringBuilder JavaDoc();
519
520                     while (ts.moveNext()) {
521                         token = ts.token();
522                         id = token.id();
523
524                         if ((id == RubyTokenId.ERROR) || (id == RubyTokenId.STRING_LITERAL) ||
525                                 (id == RubyTokenId.QUOTED_STRING_LITERAL) ||
526                                 (id == RubyTokenId.EMBEDDED_RUBY)) {
527                             sb.append(token.text());
528                         } else {
529                             break;
530                         }
531                     }
532
533                     return sb.toString();
534                 }
535             }
536         }
537
538         return null;
539     }
540
541     /**
542      * Check if the caret is inside a literal string that is associated with
543      * a require statement.
544      *
545      * @return The offset of the beginning of the require string, or -1
546      * if the offset is not inside a require string.
547      */

548     public static int getRequireStringOffset(int caretOffset, TokenHierarchy<Document JavaDoc> th) {
549         TokenSequence<?extends GsfTokenId> ts = th.tokenSequence(RubyTokenId.language());
550
551         if (ts == null) {
552             return -1;
553         }
554
555         ts.move(caretOffset);
556
557         if (!ts.moveNext() && !ts.movePrevious()) {
558             return -1;
559         }
560
561         if (ts.offset() == caretOffset) {
562             // We're looking at the offset to the RIGHT of the caret
563
// and here I care about what's on the left
564
ts.movePrevious();
565         }
566
567         Token<?extends GsfTokenId> token = ts.token();
568
569         if (token != null) {
570             TokenId id = token.id();
571
572             // Skip over embedded Ruby segments and literal strings until you find the beginning
573
while ((id == RubyTokenId.ERROR) || (id == RubyTokenId.STRING_LITERAL) ||
574                     (id == RubyTokenId.QUOTED_STRING_LITERAL) || (id == RubyTokenId.EMBEDDED_RUBY)) {
575                 ts.movePrevious();
576                 token = ts.token();
577                 id = token.id();
578             }
579
580             int stringStart = ts.offset() + token.length();
581
582             if ((id == RubyTokenId.STRING_BEGIN) || (id == RubyTokenId.QUOTED_STRING_BEGIN)) {
583                 // Completion of literal strings within require calls
584
while (ts.movePrevious()) {
585                     token = ts.token();
586
587                     id = token.id();
588
589                     if ((id == RubyTokenId.WHITESPACE) || (id == RubyTokenId.LPAREN) ||
590                             (id == RubyTokenId.STRING_LITERAL) ||
591                             (id == RubyTokenId.QUOTED_STRING_LITERAL)) {
592                         continue;
593                     }
594
595                     if (id == RubyTokenId.IDENTIFIER) {
596                         String JavaDoc text = token.text().toString();
597
598                         if (text.equals("require") || text.equals("load")) {
599                             return stringStart;
600                         } else {
601                             return -1;
602                         }
603                     } else {
604                         return -1;
605                     }
606                 }
607             }
608         }
609
610         return -1;
611     }
612
613     public static int getSingleQuotedStringOffset(int caretOffset, TokenHierarchy<Document JavaDoc> th) {
614         return getLiteralStringOffset(caretOffset, th, RubyTokenId.STRING_BEGIN);
615     }
616
617     public static int getDoubleQuotedStringOffset(int caretOffset, TokenHierarchy<Document JavaDoc> th) {
618         return getLiteralStringOffset(caretOffset, th, RubyTokenId.QUOTED_STRING_BEGIN);
619     }
620
621     public static int getRegexpOffset(int caretOffset, TokenHierarchy<Document JavaDoc> th) {
622         return getLiteralStringOffset(caretOffset, th, RubyTokenId.REGEXP_BEGIN);
623     }
624
625     /**
626      * Determine if the caret is inside a literal string, and if so, return its starting
627      * offset. Return -1 otherwise.
628      */

629     @SuppressWarnings JavaDoc("unchecked")
630     private static int getLiteralStringOffset(int caretOffset, TokenHierarchy<Document JavaDoc> th,
631         GsfTokenId begin) {
632         TokenSequence<?extends GsfTokenId> ts = th.tokenSequence(RubyTokenId.language());
633
634         if (ts == null) {
635             return -1;
636         }
637
638         ts.move(caretOffset);
639
640         if (!ts.moveNext() && !ts.movePrevious()) {
641             return -1;
642         }
643
644         if (ts.offset() == caretOffset) {
645             // We're looking at the offset to the RIGHT of the caret
646
// and here I care about what's on the left
647
ts.movePrevious();
648         }
649
650         Token<?extends GsfTokenId> token = ts.token();
651
652         if (token != null) {
653             TokenId id = token.id();
654
655             // We're within a String that has embedded Ruby. Drop into the
656
// embedded language and see if we're within a literal string there.
657
if (id == RubyTokenId.EMBEDDED_RUBY) {
658                 ts = (TokenSequence)ts.embedded();
659                 assert ts != null;
660                 ts.move(caretOffset);
661
662                 if (!ts.moveNext() && !ts.movePrevious()) {
663                     return -1;
664                 }
665
666                 token = ts.token();
667                 id = token.id();
668             }
669
670             // Skip over embedded Ruby segments and literal strings until you find the beginning
671
while ((id == RubyTokenId.ERROR) || (id == RubyTokenId.STRING_LITERAL) ||
672                     (id == RubyTokenId.QUOTED_STRING_LITERAL) ||
673                     (id == RubyTokenId.REGEXP_LITERAL) || (id == RubyTokenId.EMBEDDED_RUBY)) {
674                 ts.movePrevious();
675                 token = ts.token();
676                 id = token.id();
677             }
678
679             if (id == begin) {
680                 if (!ts.moveNext()) {
681                     return -1;
682                 }
683
684                 return ts.offset();
685             }
686         }
687
688         return -1;
689     }
690
691     /**
692      * Determine whether the given offset corresponds to a method call on another
693      * object. This would happen in these cases:
694      * Foo::|, Foo::Bar::|, Foo.|, Foo.x|, foo.|, foo.x|
695      * and not here:
696      * |, Foo|, foo|
697      * The method returns the left hand side token, if any, such as "Foo", Foo::Bar",
698      * and "foo". If not, it will return null.
699      * Note that "self" and "super" are possible return values for the lhs, which mean
700      * that you don't have a call on another object. Clients of this method should
701      * handle that return value properly (I could return null here, but clients probably
702      * want to distinguish self and super in this case so it's useful to return the info.)
703      *
704      * This method will also try to be smart such that if you have a block or array
705      * call, it will return the relevant classnames (e.g. for [1,2].x| it returns "Array").
706      */

707     @NonNull
708     public static Call getCallType(Document JavaDoc doc, TokenHierarchy<Document JavaDoc> th, int offset) {
709         TokenSequence<?extends GsfTokenId> ts = th.tokenSequence(RubyTokenId.language());
710         ts.move(offset);
711
712         if (!ts.moveNext() && !ts.movePrevious()) {
713             return Call.NONE;
714         }
715
716         if (ts.offset() == offset) {
717             // We're looking at the offset to the RIGHT of the caret
718
// position, which could be whitespace, e.g.
719
// "foo.x| " <-- looking at the whitespace
720
ts.movePrevious();
721         }
722
723         Token<?extends GsfTokenId> token = ts.token();
724
725         if (token != null) {
726             TokenId id = token.id();
727
728             // See if we're in the identifier - "x" in "foo.x"
729
// I could also be a keyword in case the prefix happens to currently
730
// match a keyword, such as "next"
731
// However, if we're at the end of the document, x. will lex . as an
732
// identifier of text ".", so handle this case specially
733
if ((id == RubyTokenId.IDENTIFIER) || id.primaryCategory().equals("keyword")) {
734                 if (!".".equals(token.text().toString()) && !ts.movePrevious()) {
735                     return Call.NONE;
736                 }
737
738                 token = ts.token();
739                 id = token.id();
740             }
741
742             // If we're not in the identifier we need to be in the dot (in "foo.x").
743
// I can't just check for tokens DOT and COLON3 because for unparseable source
744
// (like "File.|") the lexer will return the "." as an identifier.
745
if ((id != RubyTokenId.DOT) && (id != RubyTokenId.COLON3) &&
746                     !((id == RubyTokenId.IDENTIFIER) &&
747                     (token.text().toString().equals(".") || token.text().toString().equals("::")))) {
748                 return Call.LOCAL;
749             }
750
751             int lastSeparatorOffset = ts.offset();
752             int beginOffset = lastSeparatorOffset;
753
754             // Find the beginning of the expression. We'll go past keywords, identifiers
755
// and dots or double-colons
756
while (ts.movePrevious()) {
757                 token = ts.token();
758                 id = token.id();
759
760                 if (id == RubyTokenId.WHITESPACE) {
761                     break;
762                 } else if (id == RubyTokenId.RBRACKET) {
763                     // Looks like we're operating on an array, e.g.
764
// [1,2,3].each|
765
return new Call("Array", null, false);
766                 } else if (id == RubyTokenId.RBRACE) { // XXX uh oh, what about blocks? {|x|printx}.| ? type="Proc"
767
// Looks like we're operating on a hash, e.g.
768
// {1=>foo,2=>bar}.each|
769

770                     return new Call("Hash", null, false);
771                 } else if ((id == RubyTokenId.STRING_END) || (id == RubyTokenId.QUOTED_STRING_END)) {
772                     return new Call("String", null, false);
773                 } else if (id == RubyTokenId.REGEXP_END) {
774                     return new Call("Regexp", null, false);
775                 } else if (id == RubyTokenId.INT_LITERAL) {
776                     return new Call("Fixnum", null, false); // Or Bignum?
777
} else if (id == RubyTokenId.FLOAT_LITERAL) {
778                     return new Call("Float", null, false);
779                 } else if (id == RubyTokenId.TYPE_SYMBOL) {
780                     return new Call("Symbol", null, false);
781                 } else if (id == RubyTokenId.RANGE) {
782                     return new Call("Range", null, false);
783                 } else if ((id == RubyTokenId.ANY_KEYWORD) && (token.text().equals("nil"))) {
784                     return new Call("NilClass", null, false);
785                 } else if ((id == RubyTokenId.ANY_KEYWORD) && (token.text().equals("true"))) {
786                     return new Call("TrueClass", null, false);
787                 } else if ((id == RubyTokenId.ANY_KEYWORD) && (token.text().equals("false"))) {
788                     return new Call("FalseClass", null, false);
789                 } else if ((id == RubyTokenId.GLOBAL_VAR || id == RubyTokenId.INSTANCE_VAR || id == RubyTokenId.IDENTIFIER) ||
790                         id.primaryCategory().equals("keyword") || (id == RubyTokenId.DOT) ||
791                         (id == RubyTokenId.COLON3) || (id == RubyTokenId.CONSTANT) ||
792                         (id == RubyTokenId.SUPER) || (id == RubyTokenId.SELF)) {
793                     // We're building up a potential expression such as "Test::Unit" so continue looking
794
beginOffset = ts.offset();
795
796                     continue;
797                 } else if ((id == RubyTokenId.LPAREN) || (id == RubyTokenId.LBRACE) ||
798                         (id == RubyTokenId.LBRACKET)) {
799                     // It's an expression for example within a parenthesis, e.g.
800
// yield(^File.join())
801
// in this case we can do top level completion
802
// TODO: There are probably more valid contexts here
803
break;
804                 } else {
805                     // Something else - such as "getFoo().x|" - at this point we don't know the type
806
// so we'll just return unknown
807
return Call.UNKNOWN;
808                 }
809             }
810
811             if (beginOffset < lastSeparatorOffset) {
812                 try {
813                     String JavaDoc lhs = doc.getText(beginOffset, lastSeparatorOffset - beginOffset);
814
815                     if (lhs.equals("super") || lhs.equals("self")) {
816                         return new Call(lhs, lhs, false);
817                     } else if (Character.isUpperCase(lhs.charAt(0))) {
818                         return new Call(lhs, lhs, true);
819                     } else {
820                         return new Call(null, lhs, false);
821                     }
822                 } catch (BadLocationException JavaDoc ble) {
823                     Exceptions.printStackTrace(ble);
824                 }
825             } else {
826                 return Call.UNKNOWN;
827             }
828         }
829
830         return Call.LOCAL;
831     }
832
833     public static class Call {
834         /**
835          * The call is local: there is no left hand side expression; it's
836          * either a local call or an inherited or mixed-in call
837          */

838         public static final Call LOCAL = new Call(null, null, false);
839
840         /**
841          * Doesn't look like a call
842          */

843         public static final Call NONE = new Call(null, null, false);
844
845         /**
846          * The call has a complicated LHS (involving for example parentheses).
847          * The full LHS is not computed.
848          */

849         public static final Call UNKNOWN = new Call(null, null, false);
850         private String JavaDoc type;
851         private String JavaDoc lhs;
852         private boolean isStatic;
853
854         public Call(String JavaDoc type, String JavaDoc lhs, boolean isStatic) {
855             this.type = type;
856             this.lhs = lhs;
857
858             if (lhs == null) {
859                 lhs = type;
860             }
861
862             this.isStatic = isStatic;
863         }
864
865         public String JavaDoc getType() {
866             return type;
867         }
868
869         public String JavaDoc getLhs() {
870             return lhs;
871         }
872
873         public boolean isStatic() {
874             return isStatic;
875         }
876         
877         /** Return true if the lhs is a simple identifier */
878         public boolean isSimpleIdentifier() {
879             if (lhs == null) {
880                 return false;
881             }
882             
883             for (int i = 0, n = lhs.length(); i < n; i++) {
884                 char c = lhs.charAt(i);
885                 if (Character.isJavaIdentifierPart(c)) {
886                     continue;
887                 }
888                 if (c == '@' || c == '$') {
889                     continue;
890                 }
891                 return false;
892             }
893             
894             return true;
895         }
896         
897         public String JavaDoc toString() {
898             if (this == LOCAL) {
899                 return "LOCAL";
900             } else if (this == NONE) {
901                 return "NONE";
902             } else if (this == UNKNOWN) {
903                 return "UNKNOWN";
904             } else {
905                 return "Call(" + type + "," + lhs + "," + isStatic + ")";
906             }
907         }
908     }
909 }
910
Popular Tags