0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041:
0042: package org.netbeans.editor.ext;
0043:
0044: import java.util.HashMap;
0045: import javax.swing.text.Document;
0046: import javax.swing.text.Position;
0047: import org.netbeans.editor.BaseDocument;
0048: import org.netbeans.editor.Syntax;
0049: import org.netbeans.editor.TokenID;
0050: import org.netbeans.editor.TokenContextPath;
0051: import org.netbeans.editor.TokenItem;
0052: import org.netbeans.editor.Analyzer;
0053:
0054: /**
0055: * Format support presents a set of operations over the format-writer
0056: * that is specific for the given set of formatting layers.
0057: * It presents the way how to extend the low level methods
0058: * offered by the format-writer.
0059: * In general there can be more format-layers that use one type
0060: * of the format-support.
0061: *
0062: * @author Miloslav Metelka
0063: * @version 1.00
0064: */
0065:
0066: public class FormatSupport {
0067:
0068: /** Format-writer over which this support is constructed. */
0069: private FormatWriter formatWriter;
0070:
0071: public FormatSupport(FormatWriter formatWriter) {
0072: this .formatWriter = formatWriter;
0073: }
0074:
0075: /** Getter for the format-writer associated with this format-support. */
0076: public FormatWriter getFormatWriter() {
0077: return formatWriter;
0078: }
0079:
0080: public int getTabSize() {
0081: Document doc = formatWriter.getDocument();
0082: return (doc instanceof BaseDocument) ? ((BaseDocument) doc)
0083: .getTabSize() : formatWriter.getFormatter()
0084: .getTabSize();
0085: }
0086:
0087: public int getShiftWidth() {
0088: Document doc = formatWriter.getDocument();
0089: return (doc instanceof BaseDocument) ? ((BaseDocument) doc)
0090: .getShiftWidth() : formatWriter.getFormatter()
0091: .getShiftWidth();
0092: }
0093:
0094: public boolean expandTabs() {
0095: return formatWriter.getFormatter().expandTabs();
0096: }
0097:
0098: public int getSpacesPerTab() {
0099: return formatWriter.getFormatter().getSpacesPerTab();
0100: }
0101:
0102: public Object getSettingValue(String settingName) {
0103: return formatWriter.getFormatter().getSettingValue(settingName);
0104: }
0105:
0106: public Object getSettingValue(String settingName,
0107: Object defaultValue) {
0108: Object value = getSettingValue(settingName);
0109: return (value != null) ? value : defaultValue;
0110: }
0111:
0112: public boolean getSettingBoolean(String settingName,
0113: Boolean defaultValue) {
0114: return ((Boolean) getSettingValue(settingName, defaultValue))
0115: .booleanValue();
0116: }
0117:
0118: public boolean getSettingBoolean(String settingName,
0119: boolean defaultValue) {
0120: return ((Boolean) getSettingValue(settingName,
0121: (defaultValue ? Boolean.TRUE : Boolean.FALSE)))
0122: .booleanValue();
0123: }
0124:
0125: public int getSettingInteger(String settingName,
0126: Integer defaultValue) {
0127: return ((Integer) getSettingValue(settingName, defaultValue))
0128: .intValue();
0129: }
0130:
0131: public int getSettingInteger(String settingName, int defaultValue) {
0132: Object value = getSettingValue(settingName);
0133: return (value instanceof Integer) ? ((Integer) value)
0134: .intValue() : defaultValue;
0135: }
0136:
0137: /** Delegation to the same method in format-writer. */
0138: public final boolean isIndentOnly() {
0139: return formatWriter.isIndentOnly();
0140: }
0141:
0142: /** Delegation to the same method in format-writer. */
0143: public FormatTokenPosition getFormatStartPosition() {
0144: return formatWriter.getFormatStartPosition();
0145: }
0146:
0147: public FormatTokenPosition getTextStartPosition() {
0148: return formatWriter.getTextStartPosition();
0149: }
0150:
0151: /** Get the first token in chain. */
0152: public TokenItem findFirstToken(TokenItem token) {
0153: return formatWriter.findFirstToken(token);
0154: }
0155:
0156: /** Delegation to the same method in format-writer. */
0157: public TokenItem getLastToken() {
0158: return formatWriter.getLastToken();
0159: }
0160:
0161: public FormatTokenPosition getLastPosition() {
0162: TokenItem lt = findNonEmptyToken(getLastToken(), true);
0163: return (lt == null) ? null : getPosition(lt, lt.getImage()
0164: .length() - 1);
0165: }
0166:
0167: /** Delegation to the same method in format-writer. */
0168: public boolean canInsertToken(TokenItem beforeToken) {
0169: return formatWriter.canInsertToken(beforeToken);
0170: }
0171:
0172: /** Delegation to the same method in format-writer. */
0173: public TokenItem insertToken(TokenItem beforeToken,
0174: TokenID tokenID, TokenContextPath tokenContextPath,
0175: String tokenImage) {
0176: return formatWriter.insertToken(beforeToken, tokenID,
0177: tokenContextPath, tokenImage);
0178: }
0179:
0180: public void insertSpaces(TokenItem beforeToken, int spaceCount) {
0181: TokenID whitespaceTokenID = getWhitespaceTokenID();
0182: if (whitespaceTokenID == null) {
0183: throw new IllegalStateException(
0184: "Valid whitespace token-id required."); // NOI18N
0185: }
0186:
0187: insertToken(beforeToken, whitespaceTokenID, null, new String(
0188: Analyzer.getSpacesBuffer(spaceCount), 0, spaceCount));
0189: }
0190:
0191: /** Whether the token-item can be removed. It can be removed
0192: * only in case it doesn't come from the document's text
0193: * and it wasn't yet written to the underlying writer.
0194: */
0195: public boolean canRemoveToken(TokenItem token) {
0196: return formatWriter.canRemoveToken(token);
0197: }
0198:
0199: /** Remove the token-item from the chain. It can be removed
0200: * only in case it doesn't come from the document's text
0201: * and it wasn't yet written to the underlying writer.
0202:
0203: */
0204: public void removeToken(TokenItem token) {
0205: formatWriter.removeToken(token);
0206: }
0207:
0208: /** Remove all the tokens between start and end token inclusive. */
0209: public void removeTokenChain(TokenItem startToken,
0210: TokenItem endToken) {
0211: while (startToken != null && startToken != endToken) {
0212: TokenItem t = startToken.getNext();
0213: removeToken(startToken);
0214: startToken = t;
0215: }
0216: }
0217:
0218: public TokenItem splitStart(TokenItem token, int startLength,
0219: TokenID newTokenID, TokenContextPath newTokenContextPath) {
0220: return formatWriter.splitStart(token, startLength, newTokenID,
0221: newTokenContextPath);
0222: }
0223:
0224: public TokenItem splitEnd(TokenItem token, int endLength,
0225: TokenID newTokenID, TokenContextPath newTokenContextPath) {
0226: return formatWriter.splitEnd(token, endLength, newTokenID,
0227: newTokenContextPath);
0228: }
0229:
0230: public void insertString(TokenItem token, int offset, String text) {
0231: formatWriter.insertString(token, offset, text);
0232: }
0233:
0234: public void insertString(FormatTokenPosition pos, String text) {
0235: TokenItem token = pos.getToken();
0236: int offset = pos.getOffset();
0237:
0238: if (token == null) { // ending position
0239: token = getLastToken();
0240: if (token == null) {
0241: throw new IllegalStateException(
0242: "Cannot insert string. No tokens."); // NOI18N
0243: }
0244: offset = token.getImage().length();
0245: }
0246:
0247: insertString(token, offset, text);
0248: }
0249:
0250: public void remove(TokenItem token, int offset, int length) {
0251: formatWriter.remove(token, offset, length);
0252: }
0253:
0254: public void remove(FormatTokenPosition pos, int length) {
0255: remove(pos.getToken(), pos.getOffset(), length);
0256: }
0257:
0258: /** Check whether the given token has empty text and if so
0259: * start searching for token with non-empty text in the given
0260: * direction. If there's no non-empty token in the given direction
0261: * the method returns null.
0262: */
0263: public TokenItem findNonEmptyToken(TokenItem token, boolean backward) {
0264: return formatWriter.findNonEmptyToken(token, backward);
0265: }
0266:
0267: /** Get the token position that corresponds to the given token and offset.
0268: * @param token token for which the token-position is being created.
0269: * @param offset offset inside the token.
0270: */
0271: public FormatTokenPosition getPosition(TokenItem token, int offset) {
0272: return getPosition(token, offset, Position.Bias.Forward);
0273: }
0274:
0275: public FormatTokenPosition getPosition(TokenItem token, int offset,
0276: Position.Bias bias) {
0277: return formatWriter.getPosition(token, offset, bias);
0278: }
0279:
0280: /** Get the next position of the position given by parameters.
0281: * It can be either just offset increasing but it can be movement
0282: * to the next token for the token boundary.
0283: * @return next token-position or null for the EOT position
0284: */
0285: public FormatTokenPosition getNextPosition(TokenItem token,
0286: int offset, Position.Bias bias) {
0287: if (token == null) { // end of chain
0288: return null;
0289:
0290: } else { // regular token
0291: offset++;
0292:
0293: if (offset >= token.getImage().length()) {
0294: token = token.getNext();
0295: offset = 0;
0296: }
0297:
0298: return getPosition(token, offset, bias);
0299: }
0300: }
0301:
0302: /** Get the previous position of the position given by parameters.
0303: * It can be either just offset decreasing but it can be movement
0304: * to the previous token for the token boundary.
0305: * @return next token-position or null for the first position in the chain
0306: */
0307: public FormatTokenPosition getPreviousPosition(TokenItem token,
0308: int offset, Position.Bias bias) {
0309: FormatTokenPosition ret = null;
0310: if (token == null) { // end of chain
0311: TokenItem lastToken = findNonEmptyToken(getLastToken(),
0312: true);
0313: if (lastToken != null) { // regular last token
0314: ret = getPosition(lastToken, lastToken.getImage()
0315: .length() - 1, Position.Bias.Forward);
0316: }
0317:
0318: } else { // regular token
0319: offset--;
0320:
0321: if (offset < 0) {
0322: token = token.getPrevious();
0323: if (token != null) { // was first pos in first token
0324: ret = getPosition(token,
0325: token.getImage().length() - 1,
0326: Position.Bias.Forward);
0327: }
0328:
0329: } else { // still inside token
0330: ret = getPosition(token, offset, Position.Bias.Forward);
0331: }
0332: }
0333:
0334: return ret;
0335: }
0336:
0337: /** Get the token-position preceeding the given one. Use the same
0338: * bias like the given position has.
0339: */
0340: public FormatTokenPosition getPreviousPosition(
0341: FormatTokenPosition pos) {
0342: return getPreviousPosition(pos.getToken(), pos.getOffset(), pos
0343: .getBias());
0344: }
0345:
0346: /** Get the token-position preceeding the given one.
0347: * @param bias bias that the returned position will have.
0348: */
0349: public FormatTokenPosition getPreviousPosition(
0350: FormatTokenPosition pos, Position.Bias bias) {
0351: return getPreviousPosition(pos.getToken(), pos.getOffset(),
0352: bias);
0353: }
0354:
0355: public FormatTokenPosition getPreviousPosition(TokenItem token,
0356: int offset) {
0357: return getPreviousPosition(token, offset, Position.Bias.Forward);
0358: }
0359:
0360: /** Get the next successive token-position after the given one.
0361: * Use the same bias like the given position has.
0362: */
0363: public FormatTokenPosition getNextPosition(FormatTokenPosition pos) {
0364: return getNextPosition(pos.getToken(), pos.getOffset(), pos
0365: .getBias());
0366: }
0367:
0368: /** Get the token-position preceeding the given one.
0369: * @param bias bias that the returned position will have.
0370: */
0371: public FormatTokenPosition getNextPosition(FormatTokenPosition pos,
0372: Position.Bias bias) {
0373: return getNextPosition(pos.getToken(), pos.getOffset(), bias);
0374: }
0375:
0376: public FormatTokenPosition getNextPosition(TokenItem token,
0377: int offset) {
0378: return getNextPosition(token, offset, Position.Bias.Forward);
0379: }
0380:
0381: public boolean isAfter(TokenItem testedToken, TokenItem afterToken) {
0382: return formatWriter.isAfter(testedToken, afterToken);
0383: }
0384:
0385: public boolean isAfter(FormatTokenPosition testedPosition,
0386: FormatTokenPosition afterPosition) {
0387: return formatWriter.isAfter(testedPosition, afterPosition);
0388: }
0389:
0390: public boolean isChainStartPosition(FormatTokenPosition pos) {
0391: return formatWriter.isChainStartPosition(pos);
0392: }
0393:
0394: /** Whether the given token can be replaced or not. It's
0395: * identical to whether the token can be removed.
0396: */
0397: public boolean canReplaceToken(TokenItem token) {
0398: return canRemoveToken(token);
0399: }
0400:
0401: /** Replace the given token with the new token.
0402: * @param originalToken original token to be replaced.
0403: * @param tokenID token-id of the new token-item
0404: * @param tokenContextPath token-context-path of the new token-item
0405: * @param tokenImage token-text of the new token-item
0406: */
0407: public void replaceToken(TokenItem originalToken, TokenID tokenID,
0408: TokenContextPath tokenContextPath, String tokenImage) {
0409: if (!canReplaceToken(originalToken)) {
0410: throw new IllegalStateException(
0411: "Cannot insert token into chain"); // NOI18N
0412: }
0413:
0414: TokenItem next = originalToken.getNext();
0415: removeToken(originalToken);
0416: insertToken(next, tokenID, tokenContextPath, tokenImage);
0417: }
0418:
0419: /** Delegation to the same method in format-writer. */
0420: public boolean isRestartFormat() {
0421: return formatWriter.isRestartFormat();
0422: }
0423:
0424: /** Delegation to the same method in format-writer. */
0425: public void setRestartFormat(boolean restartFormat) {
0426: formatWriter.setRestartFormat(restartFormat);
0427: }
0428:
0429: /** Delegation to the same method in format-writer. */
0430: public int getIndentShift() {
0431: return formatWriter.getIndentShift();
0432: }
0433:
0434: /** Delegation to the same method in format-writer. */
0435: public void setIndentShift(int indentShift) {
0436: formatWriter.setIndentShift(indentShift);
0437: }
0438:
0439: /** Compare token-id of the compare-token with the given token-id.
0440: * Token text and token-context-path are ignored in comparison.
0441: * @param compareToken token to compare
0442: * @param withTokenID token-id with which the token's token-id is compared
0443: * @return true if the token-ids match, false otherwise
0444: */
0445: public boolean tokenEquals(TokenItem compareToken,
0446: TokenID withTokenID) {
0447: return tokenEquals(compareToken, withTokenID, null, null);
0448: }
0449:
0450: /** Compare token-id of the compare-token with the given token-id and
0451: * token-context-path. Token text is ignored in comparison.
0452: * @param compareToken token to compare
0453: * @param withTokenID token-id with which the token's token-id is compared.
0454: * @param withTokenContextPath token-context-path to which
0455: * the token's token-context-path is compared.
0456: * @return true if the token-ids match, false otherwise
0457: */
0458: public boolean tokenEquals(TokenItem compareToken,
0459: TokenID withTokenID, TokenContextPath withTokenContextPath) {
0460: return tokenEquals(compareToken, withTokenID,
0461: withTokenContextPath, null);
0462: }
0463:
0464: /** Compare token-id of the compare-token with the given token-id and
0465: * given token-text.
0466: * @param compareToken token to compare
0467: * @param withTokenID token-id with which the token's token-id is compared.
0468: * It can be null in which case the token-id is ignored from comparison.
0469: * @param withTokenContextPath token-context-path to which
0470: * the token's token-context-path is compared.
0471: * It can be null in which case the token-context-path is ignored from comparison.
0472: * @param withTokenImage token-text with which the token's token-text is compared.
0473: * It can be null in which case the token-text is ignored from comparison.
0474: * @return true if the token-ids and token-texts match, false otherwise
0475: */
0476: public boolean tokenEquals(TokenItem compareToken,
0477: TokenID withTokenID, TokenContextPath withTokenContextPath,
0478: String withTokenImage) {
0479: return (withTokenID == null || compareToken.getTokenID() == withTokenID)
0480: && (withTokenContextPath == null || compareToken
0481: .getTokenContextPath() == withTokenContextPath)
0482: && (withTokenImage == null || compareToken.getImage()
0483: .equals(withTokenImage));
0484: }
0485:
0486: /** Decide whether the character at the given offset in the given token
0487: * is whitespace.
0488: */
0489: public boolean isWhitespace(TokenItem token, int offset) {
0490: return Character.isWhitespace(token.getImage().charAt(offset));
0491: }
0492:
0493: public boolean isWhitespace(FormatTokenPosition pos) {
0494: return isWhitespace(pos.getToken(), pos.getOffset());
0495: }
0496:
0497: /** Get the starting position of the line. It searches for the new-line
0498: * character in backward direction and returns the position
0499: * of the character following
0500: * the new-line character or the first character of the first token
0501: * in the chain.
0502: * @param pos any token-position on the line.
0503: */
0504: public FormatTokenPosition findLineStart(FormatTokenPosition pos) {
0505: if (isChainStartPosition(pos)) { // begining of the chain
0506: return pos;
0507: }
0508:
0509: // Go to the previous char
0510: pos = getPreviousPosition(pos);
0511:
0512: TokenItem token = pos.getToken();
0513: int offset = pos.getOffset();
0514:
0515: while (true) {
0516: String text = token.getImage();
0517: while (offset >= 0) {
0518: if (text.charAt(offset) == '\n') {
0519: return getNextPosition(token, offset);
0520: }
0521:
0522: offset--;
0523: }
0524:
0525: if (token.getPrevious() == null) {
0526: // This is the first token in chain, return position 0
0527: return getPosition(token, 0);
0528: }
0529: token = token.getPrevious();
0530: offset = token.getImage().length() - 1;
0531: }
0532: }
0533:
0534: /** Get the ending position of the line. It searches for the new-line
0535: * character and returns the position of it
0536: * or the position after the last character of the last token
0537: * in the chain.
0538: * @param pos any token-position on the line.
0539: */
0540: public FormatTokenPosition findLineEnd(FormatTokenPosition pos) {
0541: TokenItem token = pos.getToken();
0542: int offset = pos.getOffset();
0543:
0544: if (token == null) { // end of whole chain is EOL too
0545: return pos;
0546: }
0547:
0548: while (true) {
0549: String text = token.getImage();
0550: int textLen = text.length();
0551: while (offset < textLen) {
0552: if (text.charAt(offset) == '\n') {
0553: return getPosition(token, offset);
0554: }
0555:
0556: offset++;
0557: }
0558:
0559: if (token.getNext() == null) {
0560: // This is the first token in chain, return end position
0561: return getPosition(null, 0);
0562: }
0563:
0564: token = token.getNext();
0565: offset = 0;
0566: }
0567: }
0568:
0569: /** Return the first non-whitespace character on the line
0570: * or null if there is no non-WS char on the line.
0571: */
0572: public FormatTokenPosition findLineFirstNonWhitespace(
0573: FormatTokenPosition pos) {
0574: pos = findLineStart(pos);
0575: TokenItem token = pos.getToken();
0576: int offset = pos.getOffset();
0577:
0578: if (token == null) { // no line start, no WS
0579: return null;
0580: }
0581:
0582: while (true) {
0583: String text = token.getImage();
0584: int textLen = text.length();
0585: while (offset < textLen) {
0586: if (text.charAt(offset) == '\n') {
0587: return null;
0588: }
0589:
0590: if (!isWhitespace(token, offset)) {
0591: return getPosition(token, offset);
0592: }
0593:
0594: offset++;
0595: }
0596:
0597: if (token.getNext() == null) {
0598: return null;
0599: }
0600:
0601: token = token.getNext();
0602: offset = 0;
0603: }
0604: }
0605:
0606: /** Return the ending whitespace on the line or null
0607: * if there's no such token on the given line.
0608: */
0609: public FormatTokenPosition findLineEndWhitespace(
0610: FormatTokenPosition pos) {
0611: pos = findLineEnd(pos);
0612: if (isChainStartPosition(pos)) { // empty first line
0613: return pos;
0614:
0615: } else {
0616: pos = getPreviousPosition(pos);
0617: }
0618:
0619: TokenItem token = pos.getToken();
0620: int offset = pos.getOffset();
0621: while (true) {
0622: String text = token.getImage();
0623: int textLen = text.length();
0624: while (offset >= 0) {
0625: if (offset < textLen
0626: && ((text.charAt(offset) == '\n') || !isWhitespace(
0627: token, offset))) {
0628: return getNextPosition(token, offset);
0629: }
0630:
0631: offset--;
0632: }
0633:
0634: if (token.getPrevious() == null) {
0635: // This is the first token in chain, return position 0
0636: return getPosition(token, 0);
0637: }
0638:
0639: token = token.getPrevious();
0640: offset = token.getImage().length() - 1;
0641: }
0642: }
0643:
0644: /** Get the first EOL in backward direction. The current position
0645: * is ignored by the search.
0646: * @return first EOL in backward direction or null if there
0647: * is no such token.
0648: */
0649: public FormatTokenPosition findPreviousEOL(FormatTokenPosition pos) {
0650: pos = getPreviousPosition(pos);
0651: if (pos == null) { // was the start position
0652: return null;
0653: }
0654:
0655: TokenItem token = pos.getToken();
0656: int offset = pos.getOffset();
0657:
0658: while (true) {
0659: String text = token.getImage();
0660: while (offset >= 0) {
0661: if (text.charAt(offset) == '\n') {
0662: return getPosition(token, offset);
0663: }
0664:
0665: offset--;
0666: }
0667:
0668: if (token.getPrevious() == null) {
0669: return null;
0670: }
0671:
0672: token = token.getPrevious();
0673: offset = token.getImage().length() - 1;
0674: }
0675: }
0676:
0677: /** Get the first EOL in forward direction.
0678: * @param pos starting token-position that is ignored by the search
0679: * so it can be even EOL.
0680: * @return first EOL token-position in the forward direction or null if there
0681: * is no such token.
0682: */
0683: public FormatTokenPosition findNextEOL(FormatTokenPosition pos) {
0684: pos = getNextPosition(pos);
0685: if (pos == null) {
0686: return null;
0687: }
0688:
0689: TokenItem token = pos.getToken();
0690: int offset = pos.getOffset();
0691:
0692: if (token == null) { // right at the end
0693: return null;
0694: }
0695:
0696: while (true) {
0697: String text = token.getImage();
0698: int textLen = text.length();
0699: while (offset < textLen) {
0700: if (text.charAt(offset) == '\n') {
0701: return getPosition(token, offset);
0702: }
0703:
0704: offset++;
0705: }
0706:
0707: if (token.getNext() == null) {
0708: return null;
0709: }
0710:
0711: token = token.getNext();
0712: offset = 0;
0713: }
0714: }
0715:
0716: /** Check whether there are no tokens except the ending EOL
0717: * on the given line.
0718: * @param pos any position on the line
0719: */
0720: public boolean isLineEmpty(FormatTokenPosition pos) {
0721: return findLineStart(pos).equals(findLineEnd(pos));
0722: }
0723:
0724: /** Check whether there are only the whitespace tokens
0725: * on the given line.
0726: * @param token any token on the line. It doesn't have to be the first one.
0727: */
0728: public boolean isLineWhite(FormatTokenPosition pos) {
0729: FormatTokenPosition lineStart = findLineStart(pos);
0730: return findLineEndWhitespace(pos).equals(lineStart);
0731: }
0732:
0733: /** Get the column-offset of the tokenItem on its line. The tabs
0734: * are expanded according to the tab-size.
0735: */
0736: public int getVisualColumnOffset(FormatTokenPosition pos) {
0737: TokenItem targetToken = pos.getToken();
0738: int targetOffset = pos.getOffset();
0739:
0740: FormatTokenPosition lineStart = findLineStart(pos);
0741: TokenItem token = lineStart.getToken();
0742: int offset = lineStart.getOffset();
0743:
0744: int col = 0;
0745: int tabSize = formatWriter.getFormatter().getTabSize();
0746:
0747: while (token != null) {
0748: String text = token.getImage();
0749: int textLen = text.length();
0750: while (offset < textLen) {
0751: if (token == targetToken && offset == targetOffset) {
0752: return col;
0753: }
0754:
0755: switch (text.charAt(offset)) {
0756: case '\t':
0757: col = (col + tabSize) / tabSize * tabSize;
0758: break;
0759: default:
0760: col++;
0761: }
0762:
0763: offset++;
0764: }
0765:
0766: token = token.getNext();
0767: offset = 0;
0768: }
0769:
0770: return col;
0771: }
0772:
0773: /** Get the first non-whitespace position in the given direction.
0774: * @param startPosition position at which the search starts.
0775: * For the backward search the character right at startPosition
0776: * is not considered as part of the search.
0777: * @param limitPosition the token where the search will be broken
0778: * reporting that nothing was found. It can be null to search
0779: * till the end or begining of the chain (depending on direction).
0780: * For forward search the char at the limitPosition
0781: * is not considered to be part of search,
0782: * but for backward search it is.
0783: * @param stopOnEOL whether stop and return EOL position
0784: * or continue search if EOL token is found.
0785: * @param backward whether search in backward direction.
0786: * @return first non-whitespace position or EOL or null if all the tokens
0787: * till the begining of the chain are whitespaces.
0788: */
0789: public FormatTokenPosition findNonWhitespace(
0790: FormatTokenPosition startPosition,
0791: FormatTokenPosition limitPosition, boolean stopOnEOL,
0792: boolean backward) {
0793:
0794: // Return immediately for equal positions
0795: if (startPosition.equals(limitPosition)) {
0796: return null;
0797: }
0798:
0799: if (backward) { // Backward search
0800: TokenItem limitToken;
0801: int limitOffset;
0802:
0803: if (limitPosition == null) {
0804: limitToken = null;
0805: limitOffset = 0;
0806:
0807: } else { // valid limit position
0808: limitPosition = getPreviousPosition(limitPosition);
0809: if (limitPosition == null) {
0810: limitToken = null;
0811: limitOffset = 0;
0812:
0813: } else { // valid limit position
0814: limitToken = limitPosition.getToken();
0815: limitOffset = limitPosition.getOffset();
0816: }
0817: }
0818:
0819: startPosition = getPreviousPosition(startPosition);
0820: if (startPosition == null) {
0821: return null;
0822: }
0823:
0824: TokenItem token = startPosition.getToken();
0825: int offset = startPosition.getOffset();
0826:
0827: while (true) {
0828: String text = token.getImage();
0829: while (offset >= 0) {
0830: if (stopOnEOL && text.charAt(offset) == '\n') {
0831: return null;
0832: }
0833:
0834: if (!isWhitespace(token, offset)) {
0835: return getPosition(token, offset);
0836: }
0837:
0838: if (token == limitToken && offset == limitOffset) {
0839: return null;
0840: }
0841:
0842: offset--;
0843: }
0844:
0845: token = token.getPrevious();
0846: if (token == null) {
0847: return null;
0848: }
0849: offset = token.getImage().length() - 1;
0850: }
0851:
0852: } else { // Forward direction
0853: TokenItem limitToken;
0854: int limitOffset;
0855:
0856: if (limitPosition == null) {
0857: limitToken = null;
0858: limitOffset = 0;
0859:
0860: } else { // valid limit position
0861: limitToken = limitPosition.getToken();
0862: limitOffset = limitPosition.getOffset();
0863: }
0864:
0865: TokenItem token = startPosition.getToken();
0866: int offset = startPosition.getOffset();
0867:
0868: while (true) {
0869: String text = token.getImage();
0870: int textLen = text.length();
0871: while (offset < textLen) {
0872: if (token == limitToken && offset == limitOffset) {
0873: return null;
0874: }
0875:
0876: if (stopOnEOL && text.charAt(offset) == '\n') {
0877: return null;
0878: }
0879:
0880: if (!isWhitespace(token, offset)) {
0881: return getPosition(token, offset);
0882: }
0883:
0884: offset++;
0885: }
0886:
0887: token = token.getNext();
0888: if (token == null) {
0889: return null;
0890: }
0891: offset = 0;
0892: }
0893: }
0894: }
0895:
0896: /** Get the previous token or last token if the argument is null. */
0897: public TokenItem getPreviousToken(TokenItem token) {
0898: return (token == null) ? getLastToken() : token.getPrevious();
0899: }
0900:
0901: /** Get the token-id that should be assigned to the token
0902: * that consists of the indentation whitespace only. This method should
0903: * be overriden in the descendants.
0904: */
0905: public TokenID getWhitespaceTokenID() {
0906: return null;
0907: }
0908:
0909: /** Get the valid whitespace token-id by calling <tt>getWhitespaceTokenID()</tt>.
0910: * Throw <tt>IllegalStateException</tt> if the whitespace-token-id is null.
0911: */
0912: public TokenID getValidWhitespaceTokenID() {
0913: TokenID wsID = getWhitespaceTokenID();
0914: if (wsID == null) {
0915: throw new IllegalStateException("Null whitespace token-id"); // NOI18N
0916: }
0917: return wsID;
0918: }
0919:
0920: /** Get the token-context-path that should be assigned to the token
0921: * that consists of the indentation whitespace only. This method should
0922: * be overriden in the descendants.
0923: */
0924: public TokenContextPath getWhitespaceTokenContextPath() {
0925: return null;
0926: }
0927:
0928: /** Get the valid whitespace token-context-path
0929: * by calling <tt>getWhitespaceTokenContextPath()</tt>.
0930: * Throw <tt>IllegalStateException</tt> if the whitespace-token-id is null.
0931: */
0932: public TokenContextPath getValidWhitespaceTokenContextPath() {
0933: TokenContextPath wsTCP = getWhitespaceTokenContextPath();
0934: if (wsTCP == null) {
0935: throw new IllegalStateException(
0936: "Null whitespace token-context-path"); // NOI18N
0937: }
0938: return wsTCP;
0939: }
0940:
0941: /** Check whether the given token enables modifying
0942: * of a whitespace in it. This method should be overriden
0943: * in the descendants.
0944: */
0945: public boolean canModifyWhitespace(TokenItem inToken) {
0946: return false;
0947: }
0948:
0949: /** This delegates to the same method in formatter. */
0950: public String getIndentString(int indent) {
0951: return formatWriter.getFormatter().getIndentString(indent);
0952: }
0953:
0954: /** Get the indentation of the line.
0955: * @param formatTokenPosition any position on the line.
0956: * It doesn't have to be the first one.
0957: * @param zeroForWSLine If set to true the method will return zero
0958: * in case the line consist of whitespace only. If false
0959: * the method will return the indentation even for whitespace
0960: * lines.
0961: */
0962: public int getLineIndent(FormatTokenPosition pos,
0963: boolean zeroForWSLine) {
0964: FormatTokenPosition firstNWS = findLineFirstNonWhitespace(pos);
0965: if (firstNWS == null) { // no non-WS char on the line
0966: if (zeroForWSLine) {
0967: return 0;
0968:
0969: } else { // return indent even for WS lines
0970: firstNWS = findLineEnd(pos);
0971: }
0972: }
0973:
0974: return getVisualColumnOffset(firstNWS);
0975: }
0976:
0977: /** Change the indentation of the line. This method should
0978: * be always called for all the lines because it ensures
0979: * that the indentation will contain exactly the characters from
0980: * the indentation string.
0981: * @param pos any position on the line being checked.
0982: * @param indent the indentation for the line.
0983: * @return some position on the line
0984: */
0985: public FormatTokenPosition changeLineIndent(
0986: FormatTokenPosition pos, int indent) {
0987: pos = findLineStart(pos); // go to line begining
0988: String indentString = getIndentString(indent);
0989: int indentStringLen = indentString.length();
0990: int indentStringInd = 0; // current index in the indentString
0991: TokenItem token = pos.getToken();
0992: int offset = pos.getOffset();
0993:
0994: if (token == null) { // last line is empty, append the indent string
0995: if (indentString.length() > 0) {
0996: token = insertToken(null, getValidWhitespaceTokenID(),
0997: getValidWhitespaceTokenContextPath(),
0998: indentString);
0999: }
1000: return pos; // return original end-of-chain position
1001: }
1002:
1003: while (true) {
1004:
1005: String text = token.getImage();
1006: int textLen = text.length();
1007:
1008: while (indentStringInd < indentStringLen
1009: && offset < textLen) {
1010: if (indentString.charAt(indentStringInd) != text
1011: .charAt(offset)) {
1012: if (canModifyWhitespace(token)) {
1013: // modify token text to insert the whitespace
1014: insertString(token, offset, indentString
1015: .substring(indentStringInd));
1016: offset += indentStringLen - indentStringInd; // skip WS
1017: indentStringInd = indentStringLen;
1018:
1019: } else { // cannot modify the whitespace of this token
1020: if (isWhitespace(token, offset) || offset > 0) {
1021: throw new IllegalStateException(
1022: "Cannot modify token=" + token); // NOI18N
1023:
1024: } else { // nonWS token at begining, will insert WS
1025: insertToken(
1026: token,
1027: getValidWhitespaceTokenID(),
1028: getValidWhitespaceTokenContextPath(),
1029: indentString
1030: .substring(indentStringInd));
1031: return getPosition(token, 0);
1032: }
1033: }
1034:
1035: } else { // current char matches indentString
1036: indentStringInd++; // advance inside indentString
1037: offset++;
1038: }
1039: }
1040:
1041: if (indentStringInd < indentStringLen) { // move to next token
1042: token = token.getNext();
1043: if (token == null) { // was last token, insert WS token
1044: token = insertToken(null,
1045: getValidWhitespaceTokenID(),
1046: getValidWhitespaceTokenContextPath(),
1047: indentString.substring(indentStringInd));
1048: return getPosition(token, 0);
1049:
1050: } else { // non-null token
1051: offset = 0;
1052: }
1053:
1054: } else { // indent already done, need to remove all the resting WS
1055:
1056: while (true) {
1057: text = token.getImage();
1058: textLen = text.length();
1059: int removeInd = -1;
1060:
1061: while (offset < textLen) {
1062: if (!isWhitespace(token, offset)
1063: || text.charAt(offset) == '\n') {
1064: if (removeInd >= 0) {
1065: remove(token, removeInd, offset
1066: - removeInd);
1067: offset = removeInd;
1068: }
1069:
1070: return getPosition(token, offset);
1071:
1072: } else { // whitespace char found
1073: if (removeInd < 0) {
1074: removeInd = offset;
1075: }
1076: }
1077: offset++;
1078: }
1079:
1080: if (removeInd == -1) { // nothing to remove
1081: token = token.getNext(); // was right at the end
1082:
1083: } else if (removeInd == 0) { // remove whole token
1084: TokenItem nextToken = token.getNext();
1085: removeToken(token);
1086: token = nextToken;
1087:
1088: } else { // remove just end part of token
1089: remove(token, removeInd, textLen - removeInd);
1090: token = token.getNext();
1091: }
1092: offset = 0;
1093:
1094: if (token == null) {
1095: return getPosition(null, 0);
1096: }
1097: }
1098: }
1099: }
1100: }
1101:
1102: /** Debug the current state of the chain.
1103: * @param token mark this token as current one. It can be null.
1104: */
1105: public String chainToString(TokenItem token) {
1106: return formatWriter.chainToString(token);
1107: }
1108:
1109: public String chainToString(TokenItem token, int maxDocumentTokens) {
1110: return formatWriter.chainToString(token, maxDocumentTokens);
1111: }
1112:
1113: }
|