/* * 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.ImageTokenID; import org.netbeans.editor.TokenContextPath; import org.netbeans.editor.TokenID; import org.netbeans.editor.TokenItem; /** * Extended format-support offers comment-token support, * token-and-text operations and other support. * * @author Miloslav Metelka * @version 1.00 */ public class ExtFormatSupport extends FormatSupport { public ExtFormatSupport(FormatWriter formatWriter) { super(formatWriter); } /** Find how many EOLs is between two token-position. * @param fromPosition the position from which to start counting. * If it's EOL, it's counted. * @param toPosition the ending position. If it points at EOL, * it's ignored from the total count. * It is necessary for the second position to follow * the first one. */ public int findLineDistance(FormatTokenPosition fromPosition, FormatTokenPosition toPosition) { int lineCounter = 0; TokenItem token = fromPosition.getToken(); int offset = fromPosition.getOffset(); TokenItem targetToken = toPosition.getToken(); int targetOffset = toPosition.getOffset(); // Solve special case if both positions are ending if (token == null && targetToken == null) { return 0; } while (token != null) { String text = token.getImage(); int textLen = text.length(); while (offset < textLen) { if (token == targetToken && offset == targetOffset) { return lineCounter; } if (text.charAt(offset) == '\n') { lineCounter++; } offset++; } token = token.getNext(); offset = 0; } throw new IllegalStateException("Tokens don't follow in chain."); } /** Is the given token a comment token? By default it returns * false but it can be redefined in descendants. */ public boolean isComment(TokenItem token, int offset) { return false; } public boolean isComment(FormatTokenPosition pos) { return isComment(pos.getToken(), pos.getOffset()); } /** Whether the given position is not a whitespace or comment. */ public boolean isImportant(TokenItem token, int offset) { return !isComment(token, offset) && !isWhitespace(token, offset); } public boolean isImportant(FormatTokenPosition pos) { return isImportant(pos.getToken(), pos.getOffset()); } /** Get the first position that is not whitespace and that is not comment. * @param startPosition position from which the search starts. * For the backward search the character right at startPosition * is not considered as part of the search. * @param limitPosition position 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). * @param stopOnEOL whether stop and return EOL token or continue search if * EOL token is found. * @param backward whether search in backward direction. * @return first non-whitespace token or EOL or null if all the tokens * till the begining of the chain are whitespaces. */ public FormatTokenPosition findImportant(FormatTokenPosition startPosition, FormatTokenPosition limitPosition, boolean stopOnEOL, boolean backward) { // Return immediately for equal positions if (startPosition.equals(limitPosition)) { return null; } if (backward) { 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 (isImportant(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(); if (token == null) return null; 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 (isImportant(token, offset)) { return getPosition(token, offset); } offset++; } token = token.getNext(); if (token == null) { return null; } offset = 0; } } } /** Get the first non-whitespace and non-comment token or null. * @param pos any position on the line. */ public FormatTokenPosition findLineFirstImportant(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 (isImportant(token, offset)) { return getPosition(token, offset); } offset++; } if (token.getNext() == null) { return null; } token = token.getNext(); offset = 0; } } /** Get the start of the area of line where there is only * whitespace or comment till the end of the line. * @param pos any position on the line. * Return null if there's no such area. */ public FormatTokenPosition findLineEndNonImportant(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') || isImportant(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; } } /** Insert the token that has token-id containing image, so additional * text is not necessary. */ public TokenItem insertImageToken(TokenItem beforeToken, ImageTokenID tokenID, TokenContextPath tokenContextPath) { return super.insertToken(beforeToken, tokenID, tokenContextPath, tokenID.getImage()); } /** Find the token either by token-id or token-text or both. * @param startToken token from which to start searching. For backward * search this token is excluded from the search. * @param limitToken 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 this token is not considered to be part of search, * but for backward search it is. * @param tokenID token-id to be searched. If null the token-id * of the tokens inspected will be ignored. * @param tokenImage text of the token to find. If null the text * of the tokens inspected will be ignored. * @param backward true for searching in backward direction or false * to serach in forward direction. * @return return the matching token or null if nothing was found */ public TokenItem findToken(TokenItem startToken, TokenItem limitToken, TokenID tokenID, TokenContextPath tokenContextPath, String tokenImage, boolean backward) { if (backward) { // go to the previous token for the backward search if (startToken != null && startToken == limitToken) { // empty search return null; } startToken = getPreviousToken(startToken); if (limitToken != null) { limitToken = limitToken.getPrevious(); } } while (startToken != null && startToken != limitToken) { if (tokenEquals(startToken, tokenID, tokenContextPath, tokenImage)) { return startToken; } startToken = backward ? startToken.getPrevious() : startToken.getNext(); } return null; } /** Find the first non-whitespace and non-comment token in the given * direction. This is similair to <tt>findImportant()</tt> * but it operates over the tokens. * @param startToken token from which to start searching. For backward * search this token is excluded from the search. * @param limitToken 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 this token is not considered to be part of search, * but for backward search it is. * @param backward true for searching in backward direction or false * to serach in forward direction. * @return return the matching token or null if nothing was found */ public TokenItem findImportantToken(TokenItem startToken, TokenItem limitToken, boolean backward) { if (backward) { // go to the previous token for the backward search if (startToken != null && startToken == limitToken) { // empty search return null; } startToken = getPreviousToken(startToken); if (limitToken != null) { limitToken = limitToken.getPrevious(); } } while (startToken != null && startToken != limitToken) { if (isImportant(startToken, 0)) { return startToken; } startToken = backward ? startToken.getPrevious() : startToken.getNext(); } return null; } /** This method can be used to find a matching brace token. Both * the token-id and token-text are used for comparison of the starting token. * @param startToken token from which to start. It cannot be null. * For backward search this token is ignored and the previous one is used. * @param limitToken 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 this token is not considered to be part of search, * but for backward search it is. * @param matchTokenID matching token-id for the start token. * @param matchTokenImage matching token-text for the start token. * @param backward true for searching in backward direction or false * to serach in forward direction. */ public TokenItem findMatchingToken(TokenItem startToken, TokenItem limitToken, TokenID matchTokenID, String matchTokenImage, boolean backward) { int depth = 0; TokenID startTokenID = startToken.getTokenID(); TokenContextPath startTokenContextPath = startToken.getTokenContextPath(); String startText = startToken.getImage(); // Start to search from the adjacent item TokenItem token = backward ? startToken.getPrevious() : startToken.getNext(); while (token != null && token != limitToken) { if (tokenEquals(token, matchTokenID, startTokenContextPath, matchTokenImage) ) { if (depth-- == 0) { return token; } } else if (tokenEquals(token, startTokenID, startTokenContextPath, startText) ) { depth++; } token = backward ? token.getPrevious() : token.getNext(); } return null; } public TokenItem findMatchingToken(TokenItem startToken, TokenItem limitToken, ImageTokenID matchTokenID, boolean backward) { return findMatchingToken(startToken, limitToken, matchTokenID, matchTokenID.getImage(), backward); } /** Search for any of the image tokens from the given array * and return if the token matches any item from the array. * The index of the item from the array that matched * can be found by calling <tt>getIndex()</tt> method. * It is suitable mainly for the image-token-ids. * * @param startToken token from which to start. For backward search * this token is excluded from the search. * @param limitToken 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 this token is not considered to be part of search, * but for backward search it is. * @param tokenIDArray array of the token-ids for which to search. * @param tokenContextPath context path that the found token must have. * It can be null. * @param backward true for searching in backward direction or false * to serach in forward direction. */ public TokenItem findAnyToken(TokenItem startToken, TokenItem limitToken, TokenID[] tokenIDArray, TokenContextPath tokenContextPath, boolean backward) { if (backward) { // go to the previous token for the backward search if (startToken != null && startToken == limitToken) { // empty search return null; } startToken = getPreviousToken(startToken); if (limitToken != null) { limitToken = limitToken.getPrevious(); } } while (startToken != null && startToken != limitToken) { for (int i = 0; i < tokenIDArray.length; i++) { if (tokenEquals(startToken, tokenIDArray[i], tokenContextPath)) { return startToken; } } startToken = backward ? startToken.getPrevious() : startToken.getNext(); } return null; } /** Get the index of the token in the given token-id-and-text array or -1 * if the token is not in the array. */ public int getIndex(TokenItem token, TokenID[] tokenIDArray) { for (int i = 0; i < tokenIDArray.length; i++) { if (tokenEquals(token, tokenIDArray[i])) { return i; } } return -1; // not found } /** Remove the ending whitespace from the line. * @param pos position on the line to be checked. * @return position of the EOL on the line or end of chain position */ public FormatTokenPosition removeLineEndWhitespace(FormatTokenPosition pos) { FormatTokenPosition endWS = findLineEndWhitespace(pos); if (endWS == null || endWS.getToken() == null) { // no WS on line return findLineEnd(pos); } else { // some WS on line int removeInd; TokenItem token = endWS.getToken(); int offset = endWS.getOffset(); while (true) { String text = token.getImage(); int textLen = text.length(); removeInd = offset; while (offset < textLen) { if (text.charAt(offset) == '\n') { remove(token, removeInd, offset - removeInd); return getPosition(token, removeInd); } offset++; } TokenItem nextToken = token.getNext(); if (removeInd == 0) { removeToken(token); } else { // only ending part removed remove(token, removeInd, textLen - removeInd); } token = nextToken; if (token == null) { return getPosition(null, 0); } offset = 0; } } } /** Get the character at the given position. The caller must care * about not to pass the end-of-chain position to this method. */ public char getChar(FormatTokenPosition pos) { return pos.getToken().getImage().charAt(pos.getOffset()); } /** Whether the given position is at the begining of the line. */ public boolean isLineStart(FormatTokenPosition pos) { return isChainStartPosition(pos) || getChar(getPreviousPosition(pos)) == '\n'; } public boolean isNewLine(FormatTokenPosition pos) { return (pos.getToken() != null) && getChar(pos) == '\n'; } }