001: /*
002: * Sun Public License Notice
003: *
004: * The contents of this file are subject to the Sun Public License
005: * Version 1.0 (the "License"). You may not use this file except in
006: * compliance with the License. A copy of the License is available at
007: * http://www.sun.com/
008: *
009: * The Original Code is NetBeans. The Initial Developer of the Original
010: * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun
011: * Microsystems, Inc. All Rights Reserved.
012: */
013:
014: package org.netbeans.editor.ext;
015:
016: import org.netbeans.editor.ImageTokenID;
017: import org.netbeans.editor.TokenContextPath;
018: import org.netbeans.editor.TokenID;
019: import org.netbeans.editor.TokenItem;
020:
021: /**
022: * Extended format-support offers comment-token support, token-and-text
023: * operations and other support.
024: *
025: * @author Miloslav Metelka
026: * @version 1.00
027: */
028:
029: public class ExtFormatSupport extends FormatSupport {
030:
031: public ExtFormatSupport(FormatWriter formatWriter) {
032: super (formatWriter);
033: }
034:
035: /**
036: * Find how many EOLs is between two token-position.
037: *
038: * @param fromPosition
039: * the position from which to start counting. If it's EOL, it's
040: * counted.
041: * @param toPosition
042: * the ending position. If it points at EOL, it's ignored from
043: * the total count. It is necessary for the second position to
044: * follow the first one.
045: */
046: public int findLineDistance(FormatTokenPosition fromPosition,
047: FormatTokenPosition toPosition) {
048: int lineCounter = 0;
049:
050: TokenItem token = fromPosition.getToken();
051: int offset = fromPosition.getOffset();
052: TokenItem targetToken = toPosition.getToken();
053: int targetOffset = toPosition.getOffset();
054:
055: // Solve special case if both positions are ending
056: if (token == null && targetToken == null) {
057: return 0;
058: }
059:
060: while (token != null) {
061: String text = token.getImage();
062: int textLen = text.length();
063: while (offset < textLen) {
064: if (token == targetToken && offset == targetOffset) {
065: return lineCounter;
066: }
067:
068: if (text.charAt(offset) == '\n') {
069: lineCounter++;
070: }
071:
072: offset++;
073: }
074:
075: token = token.getNext();
076: offset = 0;
077: }
078:
079: throw new IllegalStateException("Tokens don't follow in chain.");
080: }
081:
082: /**
083: * Is the given token a comment token? By default it returns false but it
084: * can be redefined in descendants.
085: */
086: public boolean isComment(TokenItem token, int offset) {
087: return false;
088: }
089:
090: public boolean isComment(FormatTokenPosition pos) {
091: return isComment(pos.getToken(), pos.getOffset());
092: }
093:
094: /** Whether the given position is not a whitespace or comment. */
095: public boolean isImportant(TokenItem token, int offset) {
096: return !isComment(token, offset)
097: && !isWhitespace(token, offset);
098: }
099:
100: public boolean isImportant(FormatTokenPosition pos) {
101: return isImportant(pos.getToken(), pos.getOffset());
102: }
103:
104: /**
105: * Get the first position that is not whitespace and that is not comment.
106: *
107: * @param startPosition
108: * position from which the search starts. For the backward search
109: * the character right at startPosition is not considered as part
110: * of the search.
111: * @param limitPosition
112: * position where the search will be broken reporting that
113: * nothing was found. It can be null to search till the end or
114: * begining of the chain (depending on direction).
115: * @param stopOnEOL
116: * whether stop and return EOL token or continue search if EOL
117: * token is found.
118: * @param backward
119: * whether search in backward direction.
120: * @return first non-whitespace token or EOL or null if all the tokens till
121: * the begining of the chain are whitespaces.
122: */
123: public FormatTokenPosition findImportant(
124: FormatTokenPosition startPosition,
125: FormatTokenPosition limitPosition, boolean stopOnEOL,
126: boolean backward) {
127: // Return immediately for equal positions
128: if (startPosition.equals(limitPosition)) {
129: return null;
130: }
131:
132: if (backward) {
133: TokenItem limitToken;
134: int limitOffset;
135:
136: if (limitPosition == null) {
137: limitToken = null;
138: limitOffset = 0;
139:
140: } else { // valid limit position
141: limitPosition = getPreviousPosition(limitPosition);
142: if (limitPosition == null) {
143: limitToken = null;
144: limitOffset = 0;
145:
146: } else { // valid limit position
147: limitToken = limitPosition.getToken();
148: limitOffset = limitPosition.getOffset();
149: }
150: }
151:
152: startPosition = getPreviousPosition(startPosition);
153: if (startPosition == null) {
154: return null;
155: }
156:
157: TokenItem token = startPosition.getToken();
158: int offset = startPosition.getOffset();
159:
160: while (true) {
161: String text = token.getImage();
162: while (offset >= 0) {
163: if (stopOnEOL && text.charAt(offset) == '\n') {
164: return null;
165: }
166:
167: if (isImportant(token, offset)) {
168: return getPosition(token, offset);
169: }
170:
171: if (token == limitToken && offset == limitOffset) {
172: return null;
173: }
174:
175: offset--;
176: }
177:
178: token = token.getPrevious();
179: if (token == null) {
180: return null;
181: }
182: offset = token.getImage().length() - 1;
183: }
184:
185: } else { // forward direction
186: TokenItem limitToken;
187: int limitOffset;
188:
189: if (limitPosition == null) {
190: limitToken = null;
191: limitOffset = 0;
192:
193: } else { // valid limit position
194: limitToken = limitPosition.getToken();
195: limitOffset = limitPosition.getOffset();
196: }
197:
198: TokenItem token = startPosition.getToken();
199: int offset = startPosition.getOffset();
200:
201: if (token == null)
202: return null;
203:
204: while (true) {
205: String text = token.getImage();
206: int textLen = text.length();
207: while (offset < textLen) {
208: if (token == limitToken && offset == limitOffset) {
209: return null;
210: }
211:
212: if (stopOnEOL && text.charAt(offset) == '\n') {
213: return null;
214: }
215:
216: if (isImportant(token, offset)) {
217: return getPosition(token, offset);
218: }
219:
220: offset++;
221: }
222:
223: token = token.getNext();
224: if (token == null) {
225: return null;
226: }
227: offset = 0;
228: }
229: }
230: }
231:
232: /**
233: * Get the first non-whitespace and non-comment token or null.
234: *
235: * @param pos
236: * any position on the line.
237: */
238: public FormatTokenPosition findLineFirstImportant(
239: FormatTokenPosition pos) {
240: pos = findLineStart(pos);
241: TokenItem token = pos.getToken();
242: int offset = pos.getOffset();
243:
244: if (token == null) { // no line start, no WS
245: return null;
246: }
247:
248: while (true) {
249: String text = token.getImage();
250: int textLen = text.length();
251: while (offset < textLen) {
252: if (text.charAt(offset) == '\n') {
253: return null;
254: }
255:
256: if (isImportant(token, offset)) {
257: return getPosition(token, offset);
258: }
259:
260: offset++;
261: }
262:
263: if (token.getNext() == null) {
264: return null;
265: }
266:
267: token = token.getNext();
268: offset = 0;
269: }
270: }
271:
272: /**
273: * Get the start of the area of line where there is only whitespace or
274: * comment till the end of the line.
275: *
276: * @param pos
277: * any position on the line. Return null if there's no such area.
278: */
279: public FormatTokenPosition findLineEndNonImportant(
280: FormatTokenPosition pos) {
281: pos = findLineEnd(pos);
282: if (isChainStartPosition(pos)) { // empty first line
283: return pos;
284:
285: } else {
286: pos = getPreviousPosition(pos);
287: }
288:
289: TokenItem token = pos.getToken();
290: int offset = pos.getOffset();
291:
292: while (true) {
293: String text = token.getImage();
294: int textLen = text.length();
295: while (offset >= 0) {
296: if (offset < textLen
297: && ((text.charAt(offset) == '\n') || isImportant(
298: token, offset))
299:
300: ) {
301: return getNextPosition(token, offset);
302: }
303:
304: offset--;
305: }
306:
307: if (token.getPrevious() == null) {
308: // This is the first token in chain, return position 0
309: return getPosition(token, 0);
310: }
311:
312: token = token.getPrevious();
313: offset = token.getImage().length() - 1;
314: }
315: }
316:
317: /**
318: * Insert the token that has token-id containing image, so additional text
319: * is not necessary.
320: */
321: public TokenItem insertImageToken(TokenItem beforeToken,
322: ImageTokenID tokenID, TokenContextPath tokenContextPath) {
323: return super .insertToken(beforeToken, tokenID,
324: tokenContextPath, tokenID.getImage());
325: }
326:
327: /**
328: * Find the token either by token-id or token-text or both.
329: *
330: * @param startToken
331: * token from which to start searching. For backward search this
332: * token is excluded from the search.
333: * @param limitToken
334: * the token where the search will be broken reporting that
335: * nothing was found. It can be null to search till the end or
336: * begining of the chain (depending on direction). For forward
337: * search this token is not considered to be part of search, but
338: * for backward search it is.
339: * @param tokenID
340: * token-id to be searched. If null the token-id of the tokens
341: * inspected will be ignored.
342: * @param tokenImage
343: * text of the token to find. If null the text of the tokens
344: * inspected will be ignored.
345: * @param backward
346: * true for searching in backward direction or false to serach in
347: * forward direction.
348: * @return return the matching token or null if nothing was found
349: */
350: public TokenItem findToken(TokenItem startToken,
351: TokenItem limitToken, TokenID tokenID,
352: TokenContextPath tokenContextPath, String tokenImage,
353: boolean backward) {
354:
355: if (backward) { // go to the previous token for the backward search
356: if (startToken != null && startToken == limitToken) { // empty
357: // search
358: return null;
359: }
360:
361: startToken = getPreviousToken(startToken);
362:
363: if (limitToken != null) {
364: limitToken = limitToken.getPrevious();
365: }
366: }
367:
368: while (startToken != null && startToken != limitToken) {
369: if (tokenEquals(startToken, tokenID, tokenContextPath,
370: tokenImage)) {
371: return startToken;
372: }
373:
374: startToken = backward ? startToken.getPrevious()
375: : startToken.getNext();
376: }
377:
378: return null;
379: }
380:
381: /**
382: * Find the first non-whitespace and non-comment token in the given
383: * direction. This is similair to <tt>findImportant()</tt> but it operates
384: * over the tokens.
385: *
386: * @param startToken
387: * token from which to start searching. For backward search this
388: * token is excluded from the search.
389: * @param limitToken
390: * the token where the search will be broken reporting that
391: * nothing was found. It can be null to search till the end or
392: * begining of the chain (depending on direction). For forward
393: * search this token is not considered to be part of search, but
394: * for backward search it is.
395: * @param backward
396: * true for searching in backward direction or false to serach in
397: * forward direction.
398: * @return return the matching token or null if nothing was found
399: */
400: public TokenItem findImportantToken(TokenItem startToken,
401: TokenItem limitToken, boolean backward) {
402:
403: if (backward) { // go to the previous token for the backward search
404: if (startToken != null && startToken == limitToken) { // empty
405: // search
406: return null;
407: }
408:
409: startToken = getPreviousToken(startToken);
410:
411: if (limitToken != null) {
412: limitToken = limitToken.getPrevious();
413: }
414: }
415:
416: while (startToken != null && startToken != limitToken) {
417: if (isImportant(startToken, 0)) {
418: return startToken;
419: }
420:
421: startToken = backward ? startToken.getPrevious()
422: : startToken.getNext();
423: }
424:
425: return null;
426: }
427:
428: /**
429: * This method can be used to find a matching brace token. Both the token-id
430: * and token-text are used for comparison of the starting token.
431: *
432: * @param startToken
433: * token from which to start. It cannot be null. For backward
434: * search this token is ignored and the previous one is used.
435: * @param limitToken
436: * the token where the search will be broken reporting that
437: * nothing was found. It can be null to search till the end or
438: * begining of the chain (depending on direction). For forward
439: * search this token is not considered to be part of search, but
440: * for backward search it is.
441: * @param matchTokenID
442: * matching token-id for the start token.
443: * @param matchTokenImage
444: * matching token-text for the start token.
445: * @param backward
446: * true for searching in backward direction or false to serach in
447: * forward direction.
448: */
449: public TokenItem findMatchingToken(TokenItem startToken,
450: TokenItem limitToken, TokenID matchTokenID,
451: String matchTokenImage, boolean backward) {
452:
453: int depth = 0;
454: TokenID startTokenID = startToken.getTokenID();
455: TokenContextPath startTokenContextPath = startToken
456: .getTokenContextPath();
457: String startText = startToken.getImage();
458:
459: // Start to search from the adjacent item
460: TokenItem token = backward ? startToken.getPrevious()
461: : startToken.getNext();
462:
463: while (token != null && token != limitToken) {
464: if (tokenEquals(token, matchTokenID, startTokenContextPath,
465: matchTokenImage)) {
466: if (depth-- == 0) {
467: return token;
468: }
469:
470: } else if (tokenEquals(token, startTokenID,
471: startTokenContextPath, startText)) {
472: depth++;
473: }
474:
475: token = backward ? token.getPrevious() : token.getNext();
476: }
477:
478: return null;
479: }
480:
481: public TokenItem findMatchingToken(TokenItem startToken,
482: TokenItem limitToken, ImageTokenID matchTokenID,
483: boolean backward) {
484: return findMatchingToken(startToken, limitToken, matchTokenID,
485: matchTokenID.getImage(), backward);
486: }
487:
488: /**
489: * Search for any of the image tokens from the given array and return if the
490: * token matches any item from the array. The index of the item from the
491: * array that matched can be found by calling <tt>getIndex()</tt> method.
492: * It is suitable mainly for the image-token-ids.
493: *
494: * @param startToken
495: * token from which to start. For backward search this token is
496: * excluded from the search.
497: * @param limitToken
498: * the token where the search will be broken reporting that
499: * nothing was found. It can be null to search till the end or
500: * begining of the chain (depending on direction). For forward
501: * search this token is not considered to be part of search, but
502: * for backward search it is.
503: * @param tokenIDArray
504: * array of the token-ids for which to search.
505: * @param tokenContextPath
506: * context path that the found token must have. It can be null.
507: * @param backward
508: * true for searching in backward direction or false to serach in
509: * forward direction.
510: */
511: public TokenItem findAnyToken(TokenItem startToken,
512: TokenItem limitToken, TokenID[] tokenIDArray,
513: TokenContextPath tokenContextPath, boolean backward) {
514:
515: if (backward) { // go to the previous token for the backward search
516: if (startToken != null && startToken == limitToken) { // empty
517: // search
518: return null;
519: }
520:
521: startToken = getPreviousToken(startToken);
522:
523: if (limitToken != null) {
524: limitToken = limitToken.getPrevious();
525: }
526: }
527:
528: while (startToken != null && startToken != limitToken) {
529: for (int i = 0; i < tokenIDArray.length; i++) {
530: if (tokenEquals(startToken, tokenIDArray[i],
531: tokenContextPath)) {
532: return startToken;
533: }
534: }
535:
536: startToken = backward ? startToken.getPrevious()
537: : startToken.getNext();
538: }
539:
540: return null;
541: }
542:
543: /**
544: * Get the index of the token in the given token-id-and-text array or -1 if
545: * the token is not in the array.
546: */
547: public int getIndex(TokenItem token, TokenID[] tokenIDArray) {
548: for (int i = 0; i < tokenIDArray.length; i++) {
549: if (tokenEquals(token, tokenIDArray[i])) {
550: return i;
551: }
552: }
553: return -1; // not found
554: }
555:
556: /**
557: * Remove the ending whitespace from the line.
558: *
559: * @param pos
560: * position on the line to be checked.
561: * @return position of the EOL on the line or end of chain position
562: */
563: public FormatTokenPosition removeLineEndWhitespace(
564: FormatTokenPosition pos) {
565: FormatTokenPosition endWS = findLineEndWhitespace(pos);
566: if (endWS == null || endWS.getToken() == null) { // no WS on line
567: return findLineEnd(pos);
568:
569: } else { // some WS on line
570: int removeInd;
571: TokenItem token = endWS.getToken();
572: int offset = endWS.getOffset();
573:
574: while (true) {
575: String text = token.getImage();
576: int textLen = text.length();
577: removeInd = offset;
578: while (offset < textLen) {
579: if (text.charAt(offset) == '\n') {
580: remove(token, removeInd, offset - removeInd);
581: return getPosition(token, removeInd);
582: }
583:
584: offset++;
585: }
586:
587: TokenItem nextToken = token.getNext();
588: if (removeInd == 0) {
589: removeToken(token);
590:
591: } else { // only ending part removed
592: remove(token, removeInd, textLen - removeInd);
593: }
594:
595: token = nextToken;
596: if (token == null) {
597: return getPosition(null, 0);
598: }
599: offset = 0;
600: }
601: }
602: }
603:
604: /**
605: * Get the character at the given position. The caller must care about not
606: * to pass the end-of-chain position to this method.
607: */
608: public char getChar(FormatTokenPosition pos) {
609: return pos.getToken().getImage().charAt(pos.getOffset());
610: }
611:
612: /** Whether the given position is at the begining of the line. */
613: public boolean isLineStart(FormatTokenPosition pos) {
614: return isChainStartPosition(pos)
615: || getChar(getPreviousPosition(pos)) == '\n';
616: }
617:
618: public boolean isNewLine(FormatTokenPosition pos) {
619: return (pos.getToken() != null) && getChar(pos) == '\n';
620: }
621:
622: }
|