blob: 01602a291eeb1b6ebe5de5d358f2adf60492fdd2 [file] [log] [blame]
nitindb12994e2005-05-26 17:34:10 +00001/*******************************************************************************
2 * Copyright (c) 2000, 2005 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 * IBM Corporation - initial API and implementation
10 *******************************************************************************/
11package org.eclipse.wst.html.ui.internal.text;
12
13// taken from package org.eclipse.jdt.internal.ui.text;
14
15import java.util.Arrays;
16
17import org.eclipse.jface.text.Assert;
18import org.eclipse.jface.text.BadLocationException;
19import org.eclipse.jface.text.IDocument;
20import org.eclipse.jface.text.IRegion;
21import org.eclipse.jface.text.ITypedRegion;
22import org.eclipse.jface.text.Region;
23import org.eclipse.jface.text.TextUtilities;
24import org.eclipse.jface.text.TypedRegion;
25
26/**
27 * Utility methods for heuristic based Java manipulations in an incomplete
28 * Java source file.
29 *
30 * <p>
31 * An instance holds some internal position in the document and is therefore
32 * not threadsafe.
33 * </p>
34 *
david_williams6cb26392005-06-24 20:13:29 +000035 * @see Eclipse 3.0
nitindb12994e2005-05-26 17:34:10 +000036 */
37final class JavaHeuristicScanner implements Symbols {
38 /**
39 * Returned by all methods when the requested position could not be found,
40 * or if a {@link BadLocationException} was thrown while scanning.
41 */
42 public static final int NOT_FOUND = -1;
43
44 /**
45 * Special bound parameter that means either -1 (backward scanning) or
46 * <code>fDocument.getLength()</code> (forward scanning).
47 */
48 public static final int UNBOUND = -2;
49
50
51 /* character constants */
52 private static final char LBRACE = '{';
53 private static final char RBRACE = '}';
54 private static final char LPAREN = '(';
55 private static final char RPAREN = ')';
56 private static final char SEMICOLON = ';';
57 private static final char COLON = ':';
58 private static final char COMMA = ',';
59 private static final char LBRACKET = '[';
60 private static final char RBRACKET = ']';
61 private static final char QUESTIONMARK = '?';
62 private static final char EQUAL = '=';
63 private static final char LANGLE = '<';
64 private static final char RANGLE = '>';
65
66 /**
67 * Specifies the stop condition, upon which the <code>scanXXX</code>
68 * methods will decide whether to keep scanning or not. This interface may
69 * implemented by clients.
70 */
71 private static abstract class StopCondition {
72 /**
73 * Instructs the scanner to return the current position.
74 *
75 * @param ch
76 * the char at the current position
77 * @param position
78 * the current position
79 * @param forward
80 * the iteration direction
81 * @return <code>true</code> if the stop condition is met.
82 */
83 public abstract boolean stop(char ch, int position, boolean forward);
84
85 /**
86 * Asks the condition to return the next position to query. The
87 * default is to return the next/previous position.
88 *
89 * @return the next position to scan
90 */
91 public int nextPosition(int position, boolean forward) {
92 return forward ? position + 1 : position - 1;
93 }
94 }
95
96 /**
97 * Stops upon a non-whitespace (as defined by
98 * {@link Character#isWhitespace(char)}) character.
99 */
100 private static class NonWhitespace extends StopCondition {
101 /*
102 * @see org.eclipse.jdt.internal.ui.text.JavaHeuristicScanner.StopCondition#stop(char)
103 */
104 public boolean stop(char ch, int position, boolean forward) {
105 return !Character.isWhitespace(ch);
106 }
107 }
108
109 /**
110 * Stops upon a non-whitespace character in the default partition.
111 *
112 * @see NonWhitespace
113 */
114 private final class NonWhitespaceDefaultPartition extends NonWhitespace {
115 /*
116 * @see org.eclipse.jdt.internal.ui.text.JavaHeuristicScanner.StopCondition#stop(char)
117 */
118 public boolean stop(char ch, int position, boolean forward) {
119 return super.stop(ch, position, true) && isDefaultPartition(position);
120 }
121
122 /*
123 * @see org.eclipse.jdt.internal.ui.text.JavaHeuristicScanner.StopCondition#nextPosition(int,
124 * boolean)
125 */
126 public int nextPosition(int position, boolean forward) {
127 ITypedRegion partition = getPartition(position);
128 if (fPartition.equals(partition.getType()))
129 return super.nextPosition(position, forward);
130
131 if (forward) {
132 int end = partition.getOffset() + partition.getLength();
133 if (position < end)
134 return end;
135 }
136 else {
137 int offset = partition.getOffset();
138 if (position > offset)
139 return offset - 1;
140 }
141 return super.nextPosition(position, forward);
142 }
143 }
144
145 /**
146 * Stops upon a non-java identifier (as defined by
147 * {@link Character#isJavaIdentifierPart(char)}) character.
148 */
149 private static class NonJavaIdentifierPart extends StopCondition {
150 /*
151 * @see org.eclipse.jdt.internal.ui.text.JavaHeuristicScanner.StopCondition#stop(char)
152 */
153 public boolean stop(char ch, int position, boolean forward) {
154 return !Character.isJavaIdentifierPart(ch);
155 }
156 }
157
158 /**
159 * Stops upon a non-java identifier character in the default partition.
160 *
161 * @see NonJavaIdentifierPart
162 */
163 private final class NonJavaIdentifierPartDefaultPartition extends NonJavaIdentifierPart {
164 /*
165 * @see org.eclipse.jdt.internal.ui.text.JavaHeuristicScanner.StopCondition#stop(char)
166 */
167 public boolean stop(char ch, int position, boolean forward) {
168 return super.stop(ch, position, true) || !isDefaultPartition(position);
169 }
170
171 /*
172 * @see org.eclipse.jdt.internal.ui.text.JavaHeuristicScanner.StopCondition#nextPosition(int,
173 * boolean)
174 */
175 public int nextPosition(int position, boolean forward) {
176 ITypedRegion partition = getPartition(position);
177 if (fPartition.equals(partition.getType()))
178 return super.nextPosition(position, forward);
179
180 if (forward) {
181 int end = partition.getOffset() + partition.getLength();
182 if (position < end)
183 return end;
184 }
185 else {
186 int offset = partition.getOffset();
187 if (position > offset)
188 return offset - 1;
189 }
190 return super.nextPosition(position, forward);
191 }
192 }
193
194 /**
195 * Stops upon a character in the default partition that matches the given
196 * character list.
197 */
198 private final class CharacterMatch extends StopCondition {
199 private final char[] fChars;
200
201 /**
202 * Creates a new instance.
203 *
204 * @param ch
205 * the single character to match
206 */
207 public CharacterMatch(char ch) {
208 this(new char[]{ch});
209 }
210
211 /**
212 * Creates a new instance.
213 *
214 * @param chars
215 * the chars to match.
216 */
217 public CharacterMatch(char[] chars) {
218 Assert.isNotNull(chars);
219 Assert.isTrue(chars.length > 0);
220 fChars = chars;
221 Arrays.sort(chars);
222 }
223
224 /*
225 * @see org.eclipse.jdt.internal.ui.text.JavaHeuristicScanner.StopCondition#stop(char,
226 * int)
227 */
228 public boolean stop(char ch, int position, boolean forward) {
229 return Arrays.binarySearch(fChars, ch) >= 0 && isDefaultPartition(position);
230 }
231
232 /*
233 * @see org.eclipse.jdt.internal.ui.text.JavaHeuristicScanner.StopCondition#nextPosition(int,
234 * boolean)
235 */
236 public int nextPosition(int position, boolean forward) {
237 ITypedRegion partition = getPartition(position);
238 if (fPartition.equals(partition.getType()))
239 return super.nextPosition(position, forward);
240
241 if (forward) {
242 int end = partition.getOffset() + partition.getLength();
243 if (position < end)
244 return end;
245 }
246 else {
247 int offset = partition.getOffset();
248 if (position > offset)
249 return offset - 1;
250 }
251 return super.nextPosition(position, forward);
252 }
253 }
254
255 /** The document being scanned. */
256 private IDocument fDocument;
257 /** The partitioning being used for scanning. */
258 private String fPartitioning;
259 /** The partition to scan in. */
260 private String fPartition;
261
262 /* internal scan state */
263
264 /** the most recently read character. */
265 private char fChar;
266 /** the most recently read position. */
267 private int fPos;
268
269 /* preset stop conditions */
270 private final StopCondition fNonWSDefaultPart = new NonWhitespaceDefaultPartition();
271 private final static StopCondition fNonWS = new NonWhitespace();
272 private final StopCondition fNonIdent = new NonJavaIdentifierPartDefaultPartition();
273
274 /**
275 * Creates a new instance.
276 *
277 * @param document
278 * the document to scan
279 * @param partitioning
280 * the partitioning to use for scanning
281 * @param partition
282 * the partition to scan in
283 */
284 public JavaHeuristicScanner(IDocument document, String partitioning, String partition) {
285 Assert.isNotNull(document);
286 Assert.isNotNull(partitioning);
287 Assert.isNotNull(partition);
288 fDocument = document;
289 fPartitioning = partitioning;
290 fPartition = partition;
291 }
292
293 /**
294 * Calls
295 * <code>this(document, IJavaPartitions.JAVA_PARTITIONING, IDocument.DEFAULT_CONTENT_TYPE)</code>.
296 *
297 * @param document
298 * the document to scan.
299 */
300 public JavaHeuristicScanner(IDocument document) {
301 this(document, IJavaPartitions.JAVA_PARTITIONING, IDocument.DEFAULT_CONTENT_TYPE);
302 }
303
304 /**
305 * Returns the most recent internal scan position.
306 *
307 * @return the most recent internal scan position.
308 */
309 public int getPosition() {
310 return fPos;
311 }
312
313 /**
314 * Returns the next token in forward direction, starting at
315 * <code>start</code>, and not extending further than
316 * <code>bound</code>. The return value is one of the constants defined
317 * in {@link Symbols}. After a call, {@link #getPosition()} will return
318 * the position just after the scanned token (i.e. the next position that
319 * will be scanned).
320 *
321 * @param start
322 * the first character position in the document to consider
323 * @param bound
324 * the first position not to consider any more
325 * @return a constant from {@link Symbols} describing the next token
326 */
327 public int nextToken(int start, int bound) {
328 int pos = scanForward(start, bound, fNonWSDefaultPart);
329 if (pos == NOT_FOUND)
330 return TokenEOF;
331
332 fPos++;
333
334 switch (fChar) {
335 case LBRACE :
336 return TokenLBRACE;
337 case RBRACE :
338 return TokenRBRACE;
339 case LBRACKET :
340 return TokenLBRACKET;
341 case RBRACKET :
342 return TokenRBRACKET;
343 case LPAREN :
344 return TokenLPAREN;
345 case RPAREN :
346 return TokenRPAREN;
347 case SEMICOLON :
348 return TokenSEMICOLON;
349 case COMMA :
350 return TokenCOMMA;
351 case QUESTIONMARK :
352 return TokenQUESTIONMARK;
353 case EQUAL :
354 return TokenEQUAL;
355 case LANGLE :
356 return TokenLESSTHAN;
357 case RANGLE :
358 return TokenGREATERTHAN;
359 }
360
361 // else
362 if (Character.isJavaIdentifierPart(fChar)) {
363 // assume an ident or keyword
364 int from = pos, to;
365 pos = scanForward(pos + 1, bound, fNonIdent);
366 if (pos == NOT_FOUND)
367 to = bound == UNBOUND ? fDocument.getLength() : bound;
368 else
369 to = pos;
370
371 String identOrKeyword;
372 try {
373 identOrKeyword = fDocument.get(from, to - from);
374 }
375 catch (BadLocationException e) {
376 return TokenEOF;
377 }
378
379 return getToken(identOrKeyword);
380
381
382 }
383 else {
384 // operators, number literals etc
385 return TokenOTHER;
386 }
387 }
388
389 /**
390 * Returns the next token in backward direction, starting at
391 * <code>start</code>, and not extending further than
392 * <code>bound</code>. The return value is one of the constants defined
393 * in {@link Symbols}. After a call, {@link #getPosition()} will return
394 * the position just before the scanned token starts (i.e. the next
395 * position that will be scanned).
396 *
397 * @param start
398 * the first character position in the document to consider
399 * @param bound
400 * the first position not to consider any more
401 * @return a constant from {@link Symbols} describing the previous token
402 */
403 public int previousToken(int start, int bound) {
404 int pos = scanBackward(start, bound, fNonWSDefaultPart);
405 if (pos == NOT_FOUND)
406 return TokenEOF;
407
408 fPos--;
409
410 switch (fChar) {
411 case LBRACE :
412 return TokenLBRACE;
413 case RBRACE :
414 return TokenRBRACE;
415 case LBRACKET :
416 return TokenLBRACKET;
417 case RBRACKET :
418 return TokenRBRACKET;
419 case LPAREN :
420 return TokenLPAREN;
421 case RPAREN :
422 return TokenRPAREN;
423 case SEMICOLON :
424 return TokenSEMICOLON;
425 case COLON :
426 return TokenCOLON;
427 case COMMA :
428 return TokenCOMMA;
429 case QUESTIONMARK :
430 return TokenQUESTIONMARK;
431 case EQUAL :
432 return TokenEQUAL;
433 case LANGLE :
434 return TokenLESSTHAN;
435 case RANGLE :
436 return TokenGREATERTHAN;
437 }
438
439 // else
440 if (Character.isJavaIdentifierPart(fChar)) {
441 // assume an ident or keyword
442 int from, to = pos + 1;
443 pos = scanBackward(pos - 1, bound, fNonIdent);
444 if (pos == NOT_FOUND)
445 from = bound == UNBOUND ? 0 : bound + 1;
446 else
447 from = pos + 1;
448
449 String identOrKeyword;
450 try {
451 identOrKeyword = fDocument.get(from, to - from);
452 }
453 catch (BadLocationException e) {
454 return TokenEOF;
455 }
456
457 return getToken(identOrKeyword);
458
459
460 }
461 else {
462 // operators, number literals etc
463 return TokenOTHER;
464 }
465
466 }
467
468 /**
469 * Returns one of the keyword constants or <code>TokenIDENT</code> for a
470 * scanned identifier.
471 *
472 * @param s
473 * a scanned identifier
474 * @return one of the constants defined in {@link Symbols}
475 */
476 private int getToken(String s) {
477 Assert.isNotNull(s);
478
479 switch (s.length()) {
480 case 2 :
481 if ("if".equals(s)) //$NON-NLS-1$
482 return TokenIF;
483 if ("do".equals(s)) //$NON-NLS-1$
484 return TokenDO;
485 break;
486 case 3 :
487 if ("for".equals(s)) //$NON-NLS-1$
488 return TokenFOR;
489 if ("try".equals(s)) //$NON-NLS-1$
490 return TokenTRY;
491 if ("new".equals(s)) //$NON-NLS-1$
492 return TokenNEW;
493 break;
494 case 4 :
495 if ("case".equals(s)) //$NON-NLS-1$
496 return TokenCASE;
497 if ("else".equals(s)) //$NON-NLS-1$
498 return TokenELSE;
499 if ("enum".equals(s)) //$NON-NLS-1$
500 return TokenENUM;
501 if ("goto".equals(s)) //$NON-NLS-1$
502 return TokenGOTO;
503 break;
504 case 5 :
505 if ("break".equals(s)) //$NON-NLS-1$
506 return TokenBREAK;
507 if ("catch".equals(s)) //$NON-NLS-1$
508 return TokenCATCH;
509 if ("class".equals(s)) //$NON-NLS-1$
510 return TokenCLASS;
511 if ("while".equals(s)) //$NON-NLS-1$
512 return TokenWHILE;
513 break;
514 case 6 :
515 if ("return".equals(s)) //$NON-NLS-1$
516 return TokenRETURN;
517 if ("static".equals(s)) //$NON-NLS-1$
518 return TokenSTATIC;
519 if ("switch".equals(s)) //$NON-NLS-1$
520 return TokenSWITCH;
521 break;
522 case 7 :
523 if ("default".equals(s)) //$NON-NLS-1$
524 return TokenDEFAULT;
525 if ("finally".equals(s)) //$NON-NLS-1$
526 return TokenFINALLY;
527 break;
528 case 9 :
529 if ("interface".equals(s)) //$NON-NLS-1$
530 return TokenINTERFACE;
531 break;
532 case 12 :
533 if ("synchronized".equals(s)) //$NON-NLS-1$
534 return TokenSYNCHRONIZED;
535 break;
536 }
537 return TokenIDENT;
538 }
539
540 /**
541 * Returns the position of the closing peer character (forward search).
542 * Any scopes introduced by opening peers are skipped. All peers accounted
543 * for must reside in the default partition.
544 *
545 * <p>
546 * Note that <code>start</code> must not point to the opening peer, but
547 * to the first character being searched.
548 * </p>
549 *
550 * @param start
551 * the start position
552 * @param openingPeer
553 * the opening peer character (e.g. '{')
554 * @param closingPeer
555 * the closing peer character (e.g. '}')
556 * @return the matching peer character position, or <code>NOT_FOUND</code>
557 */
558 public int findClosingPeer(int start, final char openingPeer, final char closingPeer) {
559 Assert.isNotNull(fDocument);
560 Assert.isTrue(start >= 0);
561
562 try {
563 int depth = 1;
564 start -= 1;
565 while (true) {
566 start = scanForward(start + 1, UNBOUND, new CharacterMatch(new char[]{openingPeer, closingPeer}));
567 if (start == NOT_FOUND)
568 return NOT_FOUND;
569
570 if (fDocument.getChar(start) == openingPeer)
571 depth++;
572 else
573 depth--;
574
575 if (depth == 0)
576 return start;
577 }
578
579 }
580 catch (BadLocationException e) {
581 return NOT_FOUND;
582 }
583 }
584
585 /**
586 * Returns the position of the opening peer character (backward search).
587 * Any scopes introduced by closing peers are skipped. All peers accounted
588 * for must reside in the default partition.
589 *
590 * <p>
591 * Note that <code>start</code> must not point to the closing peer, but
592 * to the first character being searched.
593 * </p>
594 *
595 * @param start
596 * the start position
597 * @param openingPeer
598 * the opening peer character (e.g. '{')
599 * @param closingPeer
600 * the closing peer character (e.g. '}')
601 * @return the matching peer character position, or <code>NOT_FOUND</code>
602 */
603 public int findOpeningPeer(int start, char openingPeer, char closingPeer) {
604 Assert.isTrue(start < fDocument.getLength());
605
606 try {
607 int depth = 1;
608 start += 1;
609 while (true) {
610 start = scanBackward(start - 1, UNBOUND, new CharacterMatch(new char[]{openingPeer, closingPeer}));
611 if (start == NOT_FOUND)
612 return NOT_FOUND;
613
614 if (fDocument.getChar(start) == closingPeer)
615 depth++;
616 else
617 depth--;
618
619 if (depth == 0)
620 return start;
621 }
622
623 }
624 catch (BadLocationException e) {
625 return NOT_FOUND;
626 }
627 }
628
629 /**
630 * Computes the surrounding block around <code>offset</code>. The
631 * search is started at the beginning of <code>offset</code>, i.e. an
632 * opening brace at <code>offset</code> will not be part of the
633 * surrounding block, but a closing brace will.
634 *
635 * @param offset
636 * the offset for which the surrounding block is computed
637 * @return a region describing the surrounding block, or <code>null</code>
638 * if none can be found
639 */
640 public IRegion findSurroundingBlock(int offset) {
641 if (offset < 1 || offset >= fDocument.getLength())
642 return null;
643
644 int begin = findOpeningPeer(offset - 1, LBRACE, RBRACE);
645 int end = findClosingPeer(offset, LBRACE, RBRACE);
646 if (begin == NOT_FOUND || end == NOT_FOUND)
647 return null;
648 return new Region(begin, end + 1 - begin);
649 }
650
651 /**
652 * Finds the smallest position in <code>fDocument</code> such that the
653 * position is &gt;= <code>position</code> and &lt; <code>bound</code>
654 * and <code>Character.isWhitespace(fDocument.getChar(pos))</code>
655 * evaluates to <code>false</code> and the position is in the default
656 * partition.
657 *
658 * @param position
659 * the first character position in <code>fDocument</code> to
660 * be considered
661 * @param bound
662 * the first position in <code>fDocument</code> to not
663 * consider any more, with <code>bound</code> &gt;
664 * <code>position</code>, or <code>UNBOUND</code>
665 * @return the smallest position of a non-whitespace character in [<code>position</code>,
666 * <code>bound</code>) that resides in a Java partition, or
667 * <code>NOT_FOUND</code> if none can be found
668 */
669 public int findNonWhitespaceForward(int position, int bound) {
670 return scanForward(position, bound, fNonWSDefaultPart);
671 }
672
673 /**
674 * Finds the smallest position in <code>fDocument</code> such that the
675 * position is &gt;= <code>position</code> and &lt; <code>bound</code>
676 * and <code>Character.isWhitespace(fDocument.getChar(pos))</code>
677 * evaluates to <code>false</code>.
678 *
679 * @param position
680 * the first character position in <code>fDocument</code> to
681 * be considered
682 * @param bound
683 * the first position in <code>fDocument</code> to not
684 * consider any more, with <code>bound</code> &gt;
685 * <code>position</code>, or <code>UNBOUND</code>
686 * @return the smallest position of a non-whitespace character in [<code>position</code>,
687 * <code>bound</code>), or <code>NOT_FOUND</code> if none can
688 * be found
689 */
690 public int findNonWhitespaceForwardInAnyPartition(int position, int bound) {
691 return scanForward(position, bound, fNonWS);
692 }
693
694 /**
695 * Finds the highest position in <code>fDocument</code> such that the
696 * position is &lt;= <code>position</code> and &gt; <code>bound</code>
697 * and <code>Character.isWhitespace(fDocument.getChar(pos))</code>
698 * evaluates to <code>false</code> and the position is in the default
699 * partition.
700 *
701 * @param position
702 * the first character position in <code>fDocument</code> to
703 * be considered
704 * @param bound
705 * the first position in <code>fDocument</code> to not
706 * consider any more, with <code>bound</code> &lt;
707 * <code>position</code>, or <code>UNBOUND</code>
708 * @return the highest position of a non-whitespace character in (<code>bound</code>,
709 * <code>position</code>] that resides in a Java partition, or
710 * <code>NOT_FOUND</code> if none can be found
711 */
712 public int findNonWhitespaceBackward(int position, int bound) {
713 return scanBackward(position, bound, fNonWSDefaultPart);
714 }
715
716 /**
717 * Finds the lowest position <code>p</code> in <code>fDocument</code>
718 * such that <code>start</code> &lt;= p &lt; <code>bound</code> and
719 * <code>condition.stop(fDocument.getChar(p), p)</code> evaluates to
720 * <code>true</code>.
721 *
722 * @param start
723 * the first character position in <code>fDocument</code> to
724 * be considered
725 * @param bound
726 * the first position in <code>fDocument</code> to not
727 * consider any more, with <code>bound</code> &gt;
728 * <code>start</code>, or <code>UNBOUND</code>
729 * @param condition
730 * the <code>StopCondition</code> to check
731 * @return the lowest position in [<code>start</code>,
732 * <code>bound</code>) for which <code>condition</code>
733 * holds, or <code>NOT_FOUND</code> if none can be found
734 */
735 public int scanForward(int start, int bound, StopCondition condition) {
736 Assert.isTrue(start >= 0);
737
738 if (bound == UNBOUND)
739 bound = fDocument.getLength();
740
741 Assert.isTrue(bound <= fDocument.getLength());
742
743 try {
744 fPos = start;
745 while (fPos < bound) {
746
747 fChar = fDocument.getChar(fPos);
748 if (condition.stop(fChar, fPos, true))
749 return fPos;
750
751 fPos = condition.nextPosition(fPos, true);
752 }
753 }
754 catch (BadLocationException e) {
755 }
756 return NOT_FOUND;
757 }
758
759
760 /**
761 * Finds the lowest position in <code>fDocument</code> such that the
762 * position is &gt;= <code>position</code> and &lt; <code>bound</code>
763 * and <code>fDocument.getChar(position) == ch</code> evaluates to
764 * <code>true</code> and the position is in the default partition.
765 *
766 * @param position
767 * the first character position in <code>fDocument</code> to
768 * be considered
769 * @param bound
770 * the first position in <code>fDocument</code> to not
771 * consider any more, with <code>bound</code> &gt;
772 * <code>position</code>, or <code>UNBOUND</code>
773 * @param ch
774 * the <code>char</code> to search for
775 * @return the lowest position of <code>ch</code> in (<code>bound</code>,
776 * <code>position</code>] that resides in a Java partition, or
777 * <code>NOT_FOUND</code> if none can be found
778 */
779 public int scanForward(int position, int bound, char ch) {
780 return scanForward(position, bound, new CharacterMatch(ch));
781 }
782
783 /**
784 * Finds the lowest position in <code>fDocument</code> such that the
785 * position is &gt;= <code>position</code> and &lt; <code>bound</code>
786 * and <code>fDocument.getChar(position) == ch</code> evaluates to
787 * <code>true</code> for at least one ch in <code>chars</code> and the
788 * position is in the default partition.
789 *
790 * @param position
791 * the first character position in <code>fDocument</code> to
792 * be considered
793 * @param bound
794 * the first position in <code>fDocument</code> to not
795 * consider any more, with <code>bound</code> &gt;
796 * <code>position</code>, or <code>UNBOUND</code>
797 * @param chars
798 * an array of <code>char</code> to search for
799 * @return the lowest position of a non-whitespace character in [<code>position</code>,
800 * <code>bound</code>) that resides in a Java partition, or
801 * <code>NOT_FOUND</code> if none can be found
802 */
803 public int scanForward(int position, int bound, char[] chars) {
804 return scanForward(position, bound, new CharacterMatch(chars));
805 }
806
807 /**
808 * Finds the highest position <code>p</code> in <code>fDocument</code>
809 * such that <code>bound</code> &lt; <code>p</code> &lt;=
810 * <code>start</code> and
811 * <code>condition.stop(fDocument.getChar(p), p)</code> evaluates to
812 * <code>true</code>.
813 *
814 * @param start
815 * the first character position in <code>fDocument</code> to
816 * be considered
817 * @param bound
818 * the first position in <code>fDocument</code> to not
819 * consider any more, with <code>bound</code> &lt;
820 * <code>start</code>, or <code>UNBOUND</code>
821 * @param condition
822 * the <code>StopCondition</code> to check
823 * @return the highest position in (<code>bound</code>,
824 * <code>start</code> for which <code>condition</code> holds,
825 * or <code>NOT_FOUND</code> if none can be found
826 */
827 public int scanBackward(int start, int bound, StopCondition condition) {
828 if (bound == UNBOUND)
829 bound = -1;
830
831 Assert.isTrue(bound >= -1);
832 Assert.isTrue(start < fDocument.getLength());
833
834 try {
835 fPos = start;
836 while (fPos > bound) {
837
838 fChar = fDocument.getChar(fPos);
839 if (condition.stop(fChar, fPos, false))
840 return fPos;
841
842 fPos = condition.nextPosition(fPos, false);
843 }
844 }
845 catch (BadLocationException e) {
846 }
847 return NOT_FOUND;
848 }
849
850 /**
851 * Finds the highest position in <code>fDocument</code> such that the
852 * position is &lt;= <code>position</code> and &gt; <code>bound</code>
853 * and <code>fDocument.getChar(position) == ch</code> evaluates to
854 * <code>true</code> for at least one ch in <code>chars</code> and the
855 * position is in the default partition.
856 *
857 * @param position
858 * the first character position in <code>fDocument</code> to
859 * be considered
860 * @param bound
861 * the first position in <code>fDocument</code> to not
862 * consider any more, with <code>bound</code> &lt;
863 * <code>position</code>, or <code>UNBOUND</code>
864 * @param ch
865 * the <code>char</code> to search for
866 * @return the highest position of one element in <code>chars</code> in (<code>bound</code>,
867 * <code>position</code>] that resides in a Java partition, or
868 * <code>NOT_FOUND</code> if none can be found
869 */
870 public int scanBackward(int position, int bound, char ch) {
871 return scanBackward(position, bound, new CharacterMatch(ch));
872 }
873
874 /**
875 * Finds the highest position in <code>fDocument</code> such that the
876 * position is &lt;= <code>position</code> and &gt; <code>bound</code>
877 * and <code>fDocument.getChar(position) == ch</code> evaluates to
878 * <code>true</code> for at least one ch in <code>chars</code> and the
879 * position is in the default partition.
880 *
881 * @param position
882 * the first character position in <code>fDocument</code> to
883 * be considered
884 * @param bound
885 * the first position in <code>fDocument</code> to not
886 * consider any more, with <code>bound</code> &lt;
887 * <code>position</code>, or <code>UNBOUND</code>
888 * @param chars
889 * an array of <code>char</code> to search for
890 * @return the highest position of one element in <code>chars</code> in (<code>bound</code>,
891 * <code>position</code>] that resides in a Java partition, or
892 * <code>NOT_FOUND</code> if none can be found
893 */
894 public int scanBackward(int position, int bound, char[] chars) {
895 return scanBackward(position, bound, new CharacterMatch(chars));
896 }
897
898 /**
899 * Checks whether <code>position</code> resides in a default (Java)
900 * partition of <code>fDocument</code>.
901 *
902 * @param position
903 * the position to be checked
904 * @return <code>true</code> if <code>position</code> is in the
905 * default partition of <code>fDocument</code>,
906 * <code>false</code> otherwise
907 */
908 public boolean isDefaultPartition(int position) {
909 Assert.isTrue(position >= 0);
910 Assert.isTrue(position <= fDocument.getLength());
911
912 try {
913 return fPartition.equals(TextUtilities.getContentType(fDocument, fPartitioning, position, false));
914 }
915 catch (BadLocationException e) {
916 return false;
917 }
918 }
919
920 /**
921 * Returns the partition at <code>position</code>.
922 *
923 * @param position
924 * the position to get the partition for
925 * @return the partition at <code>position</code> or a dummy zero-length
926 * partition if accessing the document fails
927 */
928 private ITypedRegion getPartition(int position) {
929 Assert.isTrue(position >= 0);
930 Assert.isTrue(position <= fDocument.getLength());
931
932 try {
933 return TextUtilities.getPartition(fDocument, fPartitioning, position, false);
934 }
935 catch (BadLocationException e) {
936 return new TypedRegion(position, 0, "__no_partition_at_all"); //$NON-NLS-1$
937 }
938
939 }
940
941 /**
942 * Checks if the line seems to be an open condition not followed by a
943 * block (i.e. an if, while, or for statement with just one following
944 * statement, see example below).
945 *
946 * <pre>
947 * if (condition)
948 * doStuff();
949 * </pre>
950 *
951 * <p>
952 * Algorithm: if the last non-WS, non-Comment code on the line is an if
953 * (condition), while (condition), for( expression), do, else, and there
954 * is no statement after that
955 * </p>
956 *
957 * @param position
958 * the insert position of the new character
959 * @param bound
960 * the lowest position to consider
961 * @return <code>true</code> if the code is a conditional statement or
962 * loop without a block, <code>false</code> otherwise
963 */
964 public boolean isBracelessBlockStart(int position, int bound) {
965 if (position < 1)
966 return false;
967
968 switch (previousToken(position, bound)) {
969 case TokenDO :
970 case TokenELSE :
971 return true;
972 case TokenRPAREN :
973 position = findOpeningPeer(fPos, LPAREN, RPAREN);
974 if (position > 0) {
975 switch (previousToken(position - 1, bound)) {
976 case TokenIF :
977 case TokenFOR :
978 case TokenWHILE :
979 return true;
980 }
981 }
982 }
983
984 return false;
985 }
986}