/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is NetBeans. The Initial Developer of the Original
* Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun
* Microsystems, Inc. All Rights Reserved.
*/
package org.netbeans.editor.ext;
import org.netbeans.editor.*;
import javax.swing.text.Document;
import javax.swing.text.Position;
/**
* Format support presents a set of operations over the format-writer
* that is specific for the given set of formatting layers.
* It presents the way how to extend the low level methods
* offered by the format-writer.
* In general there can be more format-layers that use one type
* of the format-support.
*
* @author Miloslav Metelka
* @version 1.00
*/
public class FormatSupport {
/** Format-writer over which this support is constructed. */
private FormatWriter formatWriter;
public FormatSupport(FormatWriter formatWriter) {
this.formatWriter = formatWriter;
}
/** Getter for the format-writer associated with this format-support. */
public FormatWriter getFormatWriter() {
return formatWriter;
}
public int getTabSize() {
Document doc = formatWriter.getDocument();
return (doc instanceof BaseDocument)
? ((BaseDocument)doc).getTabSize()
: formatWriter.getFormatter().getTabSize();
}
public int getShiftWidth() {
Document doc = formatWriter.getDocument();
return (doc instanceof BaseDocument)
? ((BaseDocument)doc).getShiftWidth()
: formatWriter.getFormatter().getShiftWidth();
}
public boolean expandTabs() {
return formatWriter.getFormatter().expandTabs();
}
public int getSpacesPerTab() {
return formatWriter.getFormatter().getSpacesPerTab();
}
public Object getSettingValue(String settingName) {
return formatWriter.getFormatter().getSettingValue(settingName);
}
public Object getSettingValue(String settingName, Object defaultValue) {
Object value = getSettingValue(settingName);
return (value != null) ? value : defaultValue;
}
public boolean getSettingBoolean(String settingName, Boolean defaultValue) {
return ((Boolean)getSettingValue(settingName, defaultValue)).booleanValue();
}
public boolean getSettingBoolean(String settingName, boolean defaultValue) {
return ((Boolean)getSettingValue(settingName,
(defaultValue ? Boolean.TRUE : Boolean.FALSE))).booleanValue();
}
public int getSettingInteger(String settingName, Integer defaultValue) {
return ((Integer)getSettingValue(settingName, defaultValue)).intValue();
}
public int getSettingInteger(String settingName, int defaultValue) {
Object value = getSettingValue(settingName);
return (value instanceof Integer) ? ((Integer)value).intValue() : defaultValue;
}
/** Delegation to the same method in format-writer. */
public final boolean isIndentOnly() {
return formatWriter.isIndentOnly();
}
/** Delegation to the same method in format-writer. */
public FormatTokenPosition getFormatStartPosition() {
return formatWriter.getFormatStartPosition();
}
public FormatTokenPosition getTextStartPosition() {
return formatWriter.getTextStartPosition();
}
/** Get the first token in chain. */
public TokenItem findFirstToken(TokenItem token) {
return formatWriter.findFirstToken(token);
}
/** Delegation to the same method in format-writer. */
public TokenItem getLastToken() {
return formatWriter.getLastToken();
}
public FormatTokenPosition getLastPosition() {
TokenItem lt = findNonEmptyToken(getLastToken(), true);
return (lt == null) ? null : getPosition(lt, lt.getImage().length() - 1);
}
/** Delegation to the same method in format-writer. */
public boolean canInsertToken(TokenItem beforeToken) {
return formatWriter.canInsertToken(beforeToken);
}
/** Delegation to the same method in format-writer. */
public TokenItem insertToken(TokenItem beforeToken,
TokenID tokenID, TokenContextPath tokenContextPath, String tokenImage) {
return formatWriter.insertToken(beforeToken,
tokenID, tokenContextPath, tokenImage);
}
public void insertSpaces(TokenItem beforeToken, int spaceCount) {
TokenID whitespaceTokenID = getWhitespaceTokenID();
if (whitespaceTokenID == null) {
throw new IllegalStateException("Valid whitespace token-id required."); // NOI18N
}
insertToken(beforeToken, whitespaceTokenID, null,
new String(Analyzer.getSpacesBuffer(spaceCount), 0, spaceCount));
}
/** Whether the token-item can be removed. It can be removed
* only in case it doesn't come from the document's text
* and it wasn't yet written to the underlying writer.
*/
public boolean canRemoveToken(TokenItem token) {
return formatWriter.canRemoveToken(token);
}
/** Remove the token-item from the chain. It can be removed
* only in case it doesn't come from the document's text
* and it wasn't yet written to the underlying writer.
*/
public void removeToken(TokenItem token) {
formatWriter.removeToken(token);
}
/** Remove all the tokens between start and end token inclusive. */
public void removeTokenChain(TokenItem startToken, TokenItem endToken) {
while (startToken != null && startToken != endToken) {
TokenItem t = startToken.getNext();
removeToken(startToken);
startToken = t;
}
}
public TokenItem splitStart(TokenItem token, int startLength,
TokenID newTokenID, TokenContextPath newTokenContextPath) {
return formatWriter.splitStart(token, startLength, newTokenID, newTokenContextPath);
}
public TokenItem splitEnd(TokenItem token, int endLength,
TokenID newTokenID, TokenContextPath newTokenContextPath) {
return formatWriter.splitEnd(token, endLength, newTokenID, newTokenContextPath);
}
public void insertString(TokenItem token, int offset, String text) {
formatWriter.insertString(token, offset, text);
}
public void insertString(FormatTokenPosition pos, String text) {
TokenItem token = pos.getToken();
int offset = pos.getOffset();
if (token == null) { // ending position
token = getLastToken();
if (token == null) {
throw new IllegalStateException("Cannot insert string. No tokens.");
}
offset = token.getImage().length();
}
insertString(token, offset, text);
}
public void remove(TokenItem token, int offset, int length) {
formatWriter.remove(token, offset, length);
}
public void remove(FormatTokenPosition pos, int length) {
remove(pos.getToken(), pos.getOffset(), length);
}
/** Check whether the given token has empty text and if so
* start searching for token with non-empty text in the given
* direction. If there's no non-empty token in the given direction
* the method returns null.
*/
public TokenItem findNonEmptyToken(TokenItem token, boolean backward) {
return formatWriter.findNonEmptyToken(token, backward);
}
/** Get the token position that corresponds to the given token and offset.
* @param token token for which the token-position is being created.
* @param offset offset inside the token.
*/
public FormatTokenPosition getPosition(TokenItem token, int offset) {
return getPosition(token, offset, Position.Bias.Forward);
}
public FormatTokenPosition getPosition(TokenItem token, int offset, Position.Bias bias) {
return formatWriter.getPosition(token, offset, bias);
}
/** Get the next position of the position given by parameters.
* It can be either just offset increasing but it can be movement
* to the next token for the token boundary.
* @return next token-position or null for the EOT position
*/
public FormatTokenPosition getNextPosition(TokenItem token, int offset,
Position.Bias bias) {
if (token == null) { // end of chain
return null;
} else { // regular token
offset++;
if (offset >= token.getImage().length()) {
token = token.getNext();
offset = 0;
}
return getPosition(token, offset, bias);
}
}
/** Get the previous position of the position given by parameters.
* It can be either just offset decreasing but it can be movement
* to the previous token for the token boundary.
* @return next token-position or null for the first position in the chain
*/
public FormatTokenPosition getPreviousPosition(TokenItem token, int offset,
Position.Bias bias) {
FormatTokenPosition ret = null;
if (token == null) { // end of chain
TokenItem lastToken = findNonEmptyToken(getLastToken(), true);
if (lastToken != null) { // regular last token
ret = getPosition(lastToken, lastToken.getImage().length() - 1,
Position.Bias.Forward);
}
} else { // regular token
offset--;
if (offset < 0) {
token = token.getPrevious();
if (token != null) { // was first pos in first token
ret = getPosition(token, token.getImage().length() - 1,
Position.Bias.Forward);
}
} else { // still inside token
ret = getPosition(token, offset,
Position.Bias.Forward);
}
}
return ret;
}
/** Get the token-position preceeding the given one. Use the same
* bias like the given position has.
*/
public FormatTokenPosition getPreviousPosition(FormatTokenPosition pos) {
return getPreviousPosition(pos.getToken(),
pos.getOffset(), pos.getBias());
}
/** Get the token-position preceeding the given one.
* @param bias bias that the returned position will have.
*/
public FormatTokenPosition getPreviousPosition(FormatTokenPosition pos, Position.Bias bias) {
return getPreviousPosition(pos.getToken(), pos.getOffset(), bias);
}
public FormatTokenPosition getPreviousPosition(TokenItem token, int offset) {
return getPreviousPosition(token, offset, Position.Bias.Forward);
}
/** Get the next successive token-position after the given one.
* Use the same bias like the given position has.
*/
public FormatTokenPosition getNextPosition(FormatTokenPosition pos) {
return getNextPosition(pos.getToken(),
pos.getOffset(), pos.getBias());
}
/** Get the token-position preceeding the given one.
* @param bias bias that the returned position will have.
*/
public FormatTokenPosition getNextPosition(FormatTokenPosition pos, Position.Bias bias) {
return getNextPosition(pos.getToken(), pos.getOffset(), bias);
}
public FormatTokenPosition getNextPosition(TokenItem token, int offset) {
return getNextPosition(token, offset, Position.Bias.Forward);
}
public boolean isAfter(TokenItem testedToken, TokenItem afterToken) {
return formatWriter.isAfter(testedToken, afterToken);
}
public boolean isAfter(FormatTokenPosition testedPosition,
FormatTokenPosition afterPosition) {
return formatWriter.isAfter(testedPosition, afterPosition);
}
public boolean isChainStartPosition(FormatTokenPosition pos) {
return formatWriter.isChainStartPosition(pos);
}
/** Whether the given token can be replaced or not. It's
* identical to whether the token can be removed.
*/
public boolean canReplaceToken(TokenItem token) {
return canRemoveToken(token);
}
/** Replace the given token with the new token.
* @param originalToken original token to be replaced.
* @param tokenID token-id of the new token-item
* @param tokenContextPath token-context-path of the new token-item
* @param tokenImage token-text of the new token-item
*/
public void replaceToken(TokenItem originalToken,
TokenID tokenID, TokenContextPath tokenContextPath, String tokenImage) {
if (!canReplaceToken(originalToken)) {
throw new IllegalStateException("Cannot insert token into chain");
}
TokenItem next = originalToken.getNext();
removeToken(originalToken);
insertToken(next, tokenID, tokenContextPath, tokenImage);
}
/** Delegation to the same method in format-writer. */
public boolean isRestartFormat() {
return formatWriter.isRestartFormat();
}
/** Delegation to the same method in format-writer. */
public void setRestartFormat(boolean restartFormat) {
formatWriter.setRestartFormat(restartFormat);
}
/** Delegation to the same method in format-writer. */
public int getIndentShift() {
return formatWriter.getIndentShift();
}
/** Delegation to the same method in format-writer. */
public void setIndentShift(int indentShift) {
formatWriter.setIndentShift(indentShift);
}
/** Compare token-id of the compare-token with the given token-id.
* Token text and token-context-path are ignored in comparison.
* @param compareToken token to compare
* @param withTokenID token-id with which the token's token-id is compared
* @return true if the token-ids match, false otherwise
*/
public boolean tokenEquals(TokenItem compareToken, TokenID withTokenID) {
return tokenEquals(compareToken, withTokenID, null, null);
}
/** Compare token-id of the compare-token with the given token-id and
* token-context-path. Token text is ignored in comparison.
* @param compareToken token to compare
* @param withTokenID token-id with which the token's token-id is compared.
* @param withTokenContextPath token-context-path to which
* the token's token-context-path is compared.
* @return true if the token-ids match, false otherwise
*/
public boolean tokenEquals(TokenItem compareToken, TokenID withTokenID,
TokenContextPath withTokenContextPath) {
return tokenEquals(compareToken, withTokenID, withTokenContextPath, null);
}
/** Compare token-id of the compare-token with the given token-id and
* given token-text.
* @param compareToken token to compare
* @param withTokenID token-id with which the token's token-id is compared.
* It can be null in which case the token-id is ignored from comparison.
* @param withTokenContextPath token-context-path to which
* the token's token-context-path is compared.
* It can be null in which case the token-context-path is ignored from comparison.
* @param withTokenImage token-text with which the token's token-text is compared.
* It can be null in which case the token-text is ignored from comparison.
* @return true if the token-ids and token-texts match, false otherwise
*/
public boolean tokenEquals(TokenItem compareToken, TokenID withTokenID,
TokenContextPath withTokenContextPath, String withTokenImage) {
return (withTokenID == null || compareToken.getTokenID() == withTokenID)
&& (withTokenContextPath == null
|| compareToken.getTokenContextPath() == withTokenContextPath)
&& (withTokenImage == null || compareToken.getImage().equals(withTokenImage));
}
/** Decide whether the character at the given offset in the given token
* is whitespace.
*/
public boolean isWhitespace(TokenItem token, int offset) {
return Character.isWhitespace(token.getImage().charAt(offset));
}
public boolean isWhitespace(FormatTokenPosition pos) {
return isWhitespace(pos.getToken(), pos.getOffset());
}
/** Get the starting position of the line. It searches for the new-line
* character in backward direction and returns the position
* of the character following
* the new-line character or the first character of the first token
* in the chain.
* @param pos any token-position on the line.
*/
public FormatTokenPosition findLineStart(FormatTokenPosition pos) {
if (isChainStartPosition(pos)) { // begining of the chain
return pos;
}
// Go to the previous char
pos = getPreviousPosition(pos);
TokenItem token = pos.getToken();
int offset = pos.getOffset();
while (true) {
String text = token.getImage();
while (offset >= 0) {
if (text.charAt(offset) == '\n') {
return getNextPosition(token, offset);
}
offset--;
}
if (token.getPrevious() == null) {
// This is the first token in chain, return position 0
return getPosition(token, 0);
}
token = token.getPrevious();
offset = token.getImage().length() - 1;
}
}
/** Get the ending position of the line. It searches for the new-line
* character and returns the position of it
* or the position after the last character of the last token
* in the chain.
* @param pos any token-position on the line.
*/
public FormatTokenPosition findLineEnd(FormatTokenPosition pos) {
TokenItem token = pos.getToken();
int offset = pos.getOffset();
if (token == null) { // end of whole chain is EOL too
return pos;
}
while (true) {
String text = token.getImage();
int textLen = text.length();
while (offset < textLen) {
if (text.charAt(offset) == '\n') {
return getPosition(token, offset);
}
offset++;
}
if (token.getNext() == null) {
// This is the first token in chain, return end position
return getPosition(null, 0);
}
token = token.getNext();
offset = 0;
}
}
/** Return the first non-whitespace character on the line
* or null if there is no non-WS char on the line.
*/
public FormatTokenPosition findLineFirstNonWhitespace(FormatTokenPosition pos) {
pos = findLineStart(pos);
TokenItem token = pos.getToken();
int offset = pos.getOffset();
if (token == null) { // no line start, no WS
return null;
}
while (true) {
String text = token.getImage();
int textLen = text.length();
while (offset < textLen) {
if (text.charAt(offset) == '\n') {
return null;
}
if (!isWhitespace(token, offset)) {
return getPosition(token, offset);
}
offset++;
}
if (token.getNext() == null) {
return null;
}
token = token.getNext();
offset = 0;
}
}
/** Return the ending whitespace on the line or null
* if there's no such token on the given line.
*/
public FormatTokenPosition findLineEndWhitespace(FormatTokenPosition pos) {
pos = findLineEnd(pos);
if (isChainStartPosition(pos)) { // empty first line
return pos;
} else {
pos = getPreviousPosition(pos);
}
TokenItem token = pos.getToken();
int offset = pos.getOffset();
while (true) {
String text = token.getImage();
int textLen = text.length();
while (offset >= 0) {
if (offset < textLen
&& ((text.charAt(offset) == '\n') || !isWhitespace(token, offset))
) {
return getNextPosition(token, offset);
}
offset--;
}
if (token.getPrevious() == null) {
// This is the first token in chain, return position 0
return getPosition(token, 0);
}
token = token.getPrevious();
offset = token.getImage().length() - 1;
}
}
/** Get the first EOL in backward direction. The current position
* is ignored by the search.
* @return first EOL in backward direction or null if there
* is no such token.
*/
public FormatTokenPosition findPreviousEOL(FormatTokenPosition pos) {
pos = getPreviousPosition(pos);
if (pos == null) { // was the start position
return null;
}
TokenItem token = pos.getToken();
int offset = pos.getOffset();
while (true) {
String text = token.getImage();
while (offset >= 0) {
if (text.charAt(offset) == '\n') {
return getPosition(token, offset);
}
offset--;
}
if (token.getPrevious() == null) {
return null;
}
token = token.getPrevious();
offset = token.getImage().length() - 1;
}
}
/** Get the first EOL in forward direction.
* @param pos starting token-position that is ignored by the search
* so it can be even EOL.
* @return first EOL token-position in the forward direction or null if there
* is no such token.
*/
public FormatTokenPosition findNextEOL(FormatTokenPosition pos) {
pos = getNextPosition(pos);
if (pos == null) {
return null;
}
TokenItem token = pos.getToken();
int offset = pos.getOffset();
if (token == null) { // right at the end
return null;
}
while (true) {
String text = token.getImage();
int textLen = text.length();
while (offset < textLen) {
if (text.charAt(offset) == '\n') {
return getPosition(token, offset);
}
offset++;
}
if (token.getNext() == null) {
return null;
}
token = token.getNext();
offset = 0;
}
}
/** Check whether there are no tokens except the ending EOL
* on the given line.
* @param pos any position on the line
*/
public boolean isLineEmpty(FormatTokenPosition pos) {
return findLineStart(pos).equals(findLineEnd(pos));
}
/** Check whether there are only the whitespace tokens
* on the given line.
* @param token any token on the line. It doesn't have to be the first one.
*/
public boolean isLineWhite(FormatTokenPosition pos) {
FormatTokenPosition lineStart = findLineStart(pos);
return findLineEndWhitespace(pos).equals(lineStart);
}
/** Get the column-offset of the tokenItem on its line. The tabs
* are expanded according to the tab-size.
*/
public int getVisualColumnOffset(FormatTokenPosition pos) {
TokenItem targetToken = pos.getToken();
int targetOffset = pos.getOffset();
FormatTokenPosition lineStart = findLineStart(pos);
TokenItem token = lineStart.getToken();
int offset = lineStart.getOffset();
int col = 0;
int tabSize = formatWriter.getFormatter().getTabSize();
while (token != null) {
String text = token.getImage();
int textLen = text.length();
while (offset < textLen) {
if (token == targetToken && offset == targetOffset) {
return col;
}
switch (text.charAt(offset)) {
case '\t':
col = (col + tabSize) / tabSize * tabSize;
break;
default:
col++;
}
offset++;
}
token = token.getNext();
offset = 0;
}
return col;
}
/** Get the first non-whitespace position in the given direction.
* @param startPosition position at which the search starts.
* For the backward search the character right at startPosition
* is not considered as part of the search.
* @param limitPosition the token where the search will be broken
* reporting that nothing was found. It can be null to search
* till the end or begining of the chain (depending on direction).
* For forward search the char at the limitPosition
* is not considered to be part of search,
* but for backward search it is.
* @param stopOnEOL whether stop and return EOL position
* or continue search if EOL token is found.
* @param backward whether search in backward direction.
* @return first non-whitespace position or EOL or null if all the tokens
* till the begining of the chain are whitespaces.
*/
public FormatTokenPosition findNonWhitespace(FormatTokenPosition startPosition,
FormatTokenPosition limitPosition, boolean stopOnEOL, boolean backward) {
// Return immediately for equal positions
if (startPosition.equals(limitPosition)) {
return null;
}
if (backward) { // Backward search
TokenItem limitToken;
int limitOffset;
if (limitPosition == null) {
limitToken = null;
limitOffset = 0;
} else { // valid limit position
limitPosition = getPreviousPosition(limitPosition);
if (limitPosition == null) {
limitToken = null;
limitOffset = 0;
} else { // valid limit position
limitToken = limitPosition.getToken();
limitOffset = limitPosition.getOffset();
}
}
startPosition = getPreviousPosition(startPosition);
if (startPosition == null) {
return null;
}
TokenItem token = startPosition.getToken();
int offset = startPosition.getOffset();
while (true) {
String text = token.getImage();
while (offset >= 0) {
if (stopOnEOL && text.charAt(offset) == '\n') {
return null;
}
if (!isWhitespace(token, offset)) {
return getPosition(token, offset);
}
if (token == limitToken && offset == limitOffset) {
return null;
}
offset--;
}
token = token.getPrevious();
if (token == null) {
return null;
}
offset = token.getImage().length() - 1;
}
} else { // Forward direction
TokenItem limitToken;
int limitOffset;
if (limitPosition == null) {
limitToken = null;
limitOffset = 0;
} else { // valid limit position
limitToken = limitPosition.getToken();
limitOffset = limitPosition.getOffset();
}
TokenItem token = startPosition.getToken();
int offset = startPosition.getOffset();
while (true) {
String text = token.getImage();
int textLen = text.length();
while (offset < textLen) {
if (token == limitToken && offset == limitOffset) {
return null;
}
if (stopOnEOL && text.charAt(offset) == '\n') {
return null;
}
if (!isWhitespace(token, offset)) {
return getPosition(token, offset);
}
offset++;
}
token = token.getNext();
if (token == null) {
return null;
}
offset = 0;
}
}
}
/** Get the previous token or last token if the argument is null. */
public TokenItem getPreviousToken(TokenItem token) {
return (token == null) ? getLastToken() : token.getPrevious();
}
/** Get the token-id that should be assigned to the token
* that consists of the indentation whitespace only. This method should
* be overriden in the descendants.
*/
public TokenID getWhitespaceTokenID() {
return null;
}
/** Get the valid whitespace token-id by calling <tt>getWhitespaceTokenID()</tt>.
* Throw <tt>IllegalStateException</tt> if the whitespace-token-id is null.
*/
public TokenID getValidWhitespaceTokenID() {
TokenID wsID = getWhitespaceTokenID();
if (wsID == null) {
throw new IllegalStateException("Null whitespace token-id");
}
return wsID;
}
/** Get the token-context-path that should be assigned to the token
* that consists of the indentation whitespace only. This method should
* be overriden in the descendants.
*/
public TokenContextPath getWhitespaceTokenContextPath() {
return null;
}
/** Get the valid whitespace token-context-path
* by calling <tt>getWhitespaceTokenContextPath()</tt>.
* Throw <tt>IllegalStateException</tt> if the whitespace-token-id is null.
*/
public TokenContextPath getValidWhitespaceTokenContextPath() {
TokenContextPath wsTCP = getWhitespaceTokenContextPath();
if (wsTCP == null) {
throw new IllegalStateException("Null whitespace token-context-path");
}
return wsTCP;
}
/** Check whether the given token enables modifying
* of a whitespace in it. This method should be overriden
* in the descendants.
*/
public boolean canModifyWhitespace(TokenItem inToken) {
return false;
}
/** This delegates to the same method in formatter. */
public String getIndentString(int indent) {
return formatWriter.getFormatter().getIndentString(indent);
}
/** Get the indentation of the line.
* @param formatTokenPosition any position on the line.
* It doesn't have to be the first one.
* @param zeroForWSLine If set to true the method will return zero
* in case the line consist of whitespace only. If false
* the method will return the indentation even for whitespace
* lines.
*/
public int getLineIndent(FormatTokenPosition pos, boolean zeroForWSLine) {
FormatTokenPosition firstNWS = findLineFirstNonWhitespace(pos);
if (firstNWS == null) { // no non-WS char on the line
if (zeroForWSLine) {
return 0;
} else { // return indent even for WS lines
firstNWS = findLineEnd(pos);
}
}
return getVisualColumnOffset(firstNWS);
}
/** Change the indentation of the line. This method should
* be always called for all the lines because it ensures
* that the indentation will contain exactly the characters from
* the indentation string.
* @param pos any position on the line being checked.
* @param indent the indentation for the line.
* @return some position on the line
*/
public FormatTokenPosition changeLineIndent(FormatTokenPosition pos, int indent) {
pos = findLineStart(pos); // go to line begining
String indentString = getIndentString(indent);
int indentStringLen = indentString.length();
int indentStringInd = 0; // current index in the indentString
TokenItem token = pos.getToken();
int offset = pos.getOffset();
if (token == null) { // last line is empty, append the indent string
if (indentString.length() > 0) {
token = insertToken(null, getValidWhitespaceTokenID(),
getValidWhitespaceTokenContextPath(), indentString);
}
return pos; // return original end-of-chain position
}
while (true) {
String text = token.getImage();
int textLen = text.length();
while (indentStringInd < indentStringLen && offset < textLen) {
if (indentString.charAt(indentStringInd) != text.charAt(offset)) {
if (canModifyWhitespace(token)) {
// modify token text to insert the whitespace
insertString(token, offset, indentString.substring(indentStringInd));
offset += indentStringLen - indentStringInd; // skip WS
indentStringInd = indentStringLen;
} else { // cannot modify the whitespace of this token
if (isWhitespace(token, offset) || offset > 0) {
throw new IllegalStateException(
"Cannot modify token=" + token); // NOI18N
} else { // nonWS token at begining, will insert WS
insertToken(token, getValidWhitespaceTokenID(),
getValidWhitespaceTokenContextPath(),
indentString.substring(indentStringInd)
);
return getPosition(token, 0);
}
}
} else { // current char matches indentString
indentStringInd++; // advance inside indentString
offset++;
}
}
if (indentStringInd < indentStringLen) { // move to next token
token = token.getNext();
if (token == null) { // was last token, insert WS token
token = insertToken(null, getValidWhitespaceTokenID(),
getValidWhitespaceTokenContextPath(),
indentString.substring(indentStringInd)
);
return getPosition(token, 0);
} else { // non-null token
offset = 0;
}
} else { // indent already done, need to remove all the resting WS
while (true) {
text = token.getImage();
textLen = text.length();
int removeInd = -1;
while (offset < textLen) {
if (!isWhitespace(token, offset) || text.charAt(offset) == '\n') {
if (removeInd >= 0) {
remove(token, removeInd, offset - removeInd);
offset = removeInd;
}
return getPosition(token, offset);
} else { // whitespace char found
if (removeInd < 0) {
removeInd = offset;
}
}
offset++;
}
if (removeInd == -1) { // nothing to remove
token = token.getNext(); // was right at the end
} else if (removeInd == 0) { // remove whole token
TokenItem nextToken = token.getNext();
removeToken(token);
token = nextToken;
} else { // remove just end part of token
remove(token, removeInd, textLen - removeInd);
token = token.getNext();
}
offset = 0;
if (token == null) {
return getPosition(null, 0);
}
}
}
}
}
/** Debug the current state of the chain.
* @param token mark this token as current one. It can be null.
*/
public String chainToString(TokenItem token) {
return formatWriter.chainToString(token);
}
public String chainToString(TokenItem token, int maxDocumentTokens) {
return formatWriter.chainToString(token, maxDocumentTokens);
}
}