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