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