blob: 05029cdea7054f90eeb1a05584d3af1bbb83ce95 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2004 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.core.dom.rewrite;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
import org.eclipse.jface.text.Assert;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DefaultLineTracker;
import org.eclipse.jface.text.ILineTracker;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants;
import org.eclipse.text.edits.ReplaceEdit;
/**
* Helper class to provide String manipulation functions dealing with indents
*/
public class Indents {
private Indents() {
// don't instanciate
}
/**
* Indent char is a space char but not a line delimiters.
* <code>== Character.isWhitespace(ch) && ch != '\n' && ch != '\r'</code>
* @param ch
* @return Returns true if this the character is a indent delimiter character
*/
public static boolean isIndentChar(char ch) {
return Character.isWhitespace(ch) && !isLineDelimiterChar(ch);
}
/**
* Line delimiter chars are '\n' and '\r'.
* @param ch The character to test
* @return Returns true if this the character is a line delimiter character
*/
public static boolean isLineDelimiterChar(char ch) {
return ch == '\n' || ch == '\r';
}
/**
* Returns the indent of the given string in indentation units. Odd spaces
* are not counted.
*
* @param line the text line
* @param tabWidth the width of the '\t' character in space equivalents
* @param indentWidth the width of one indentation unit in space equivalents
* @since 3.1
*/
public static int computeIndentUnits(String line, int tabWidth, int indentWidth) {
if (indentWidth == 0)
return -1;
int visualLength= measureIndentLength(line, tabWidth);
return visualLength / indentWidth;
}
/**
* Computes the visual length of the indentation of a
* <code>CharSequence</code>, counting a tab character as the size until
* the next tab stop and every other whitespace character as one.
*
* @param line the string to measure the indent of
* @param tabSize the visual size of a tab in space equivalents
* @return the visual length of the indentation of <code>line</code>
* @since 3.1
*/
public static int measureIndentLength(CharSequence line, int tabSize) {
int length= 0;
int max= line.length();
for (int i= 0; i < max; i++) {
char ch= line.charAt(i);
if (ch == '\t') {
int reminder= length % tabSize;
length += tabSize - reminder;
} else if (isIndentChar(ch)) {
length++;
} else {
return length;
}
}
return length;
}
/**
* Removes the given number of indents from the line. Asserts that the given line
* has the requested number of indents. If <code>indentsToRemove <= 0</code>
* the line is returned.
*
* @since 3.1
*/
public static String trimIndent(String line, int indentsToRemove, int tabWidth, int indentWidth) {
if (line == null || indentsToRemove <= 0)
return line;
final int spaceEquivalentsToRemove= indentsToRemove * indentWidth;
int start= 0;
int spaceEquivalents= 0;
int size= line.length();
String prefix= null;
for (int i= 0; i < size; i++) {
char c= line.charAt(i);
if (c == '\t') {
int remainder= spaceEquivalents % tabWidth;
spaceEquivalents += tabWidth - remainder;
} else if (isIndentChar(c)) {
spaceEquivalents++;
} else {
// Assert.isTrue(false, "Line does not have requested number of indents"); //$NON-NLS-1$
start= i;
break;
}
if (spaceEquivalents == spaceEquivalentsToRemove) {
start= i + 1;
break;
}
if (spaceEquivalents > spaceEquivalentsToRemove) {
// can happen if tabSize > indentSize, e.g tabsize==8, indent==4, indentsToRemove==1, line prefixed with one tab
// this implements the third option
start= i + 1; // remove the tab
// and add the missing spaces
char[] missing= new char[spaceEquivalents - spaceEquivalentsToRemove];
Arrays.fill(missing, ' ');
prefix= new String(missing);
break;
}
}
String trimmed;
if (start == size)
trimmed= ""; //$NON-NLS-1$
else
trimmed= line.substring(start);
if (prefix == null)
return trimmed;
return prefix + trimmed;
}
/**
* Returns that part of the indentation of <code>line</code> that makes up
* a multiple of indentation units.
*
* @param line the line to scan
* @param tabWidth the size of one tab in space equivalents
* @return the indent part of <code>line</code>, but no odd spaces
* @since 3.1
*/
public static String getIndentString(String line, int tabWidth, int indentWidth) {
int size= line.length();
int end= 0;
int spaceEquivs= 0;
int characters= 0;
for (int i= 0; i < size; i++) {
char c= line.charAt(i);
if (c == '\t') {
int remainder= spaceEquivs % tabWidth;
spaceEquivs += tabWidth - remainder;
characters++;
} else if (isIndentChar(c)) {
spaceEquivs++;
characters++;
} else {
break;
}
if (spaceEquivs >= indentWidth) {
end += characters;
characters= 0;
spaceEquivs= spaceEquivs % indentWidth;
}
}
if (end == 0)
return ""; //$NON-NLS-1$
else if (end == size)
return line;
else
return line.substring(0, end);
}
/**
* Returns the length of the string representing the number of
* indents in the given string <code>line</code>. Returns
* <code>-1<code> if the line isn't prefixed with an indent of
* the given number of indents.
* @since 3.1
*/
public static int computeIndentLength(String line, int numberOfIndents, int tabWidth, int indentWidth) {
Assert.isTrue(numberOfIndents >= 0);
Assert.isTrue(tabWidth >= 0);
Assert.isTrue(indentWidth >= 0);
int spaceEquivalents= numberOfIndents * indentWidth;
int size= line.length();
int result= -1;
int blanks= 0;
for (int i= 0; i < size && blanks < spaceEquivalents; i++) {
char c= line.charAt(i);
if (c == '\t') {
int remainder= blanks % tabWidth;
blanks += tabWidth - remainder;
} else if (isIndentChar(c)) {
blanks++;
} else {
break;
}
result= i;
}
if (blanks < spaceEquivalents)
return -1;
return result + 1;
}
/**
* Change the indent of, possible muti-line, code range. The current indent is removed, a new indent added.
* The first line of the code will not be changed. (It is considered to have no indent as it might start in
* the middle of a line)
* @since 3.1
*/
public static String changeIndent(String code, int codeIndentLevel, int tabWidth, int indentWidth, String newIndent, String lineDelim) {
try {
ILineTracker tracker= new DefaultLineTracker();
tracker.set(code);
int nLines= tracker.getNumberOfLines();
if (nLines == 1) {
return code;
}
StringBuffer buf= new StringBuffer();
for (int i= 0; i < nLines; i++) {
IRegion region= tracker.getLineInformation(i);
int start= region.getOffset();
int end= start + region.getLength();
String line= code.substring(start, end);
if (i == 0) { // no indent for first line (contained in the formatted string)
buf.append(line);
} else { // no new line after last line
buf.append(lineDelim);
buf.append(newIndent);
buf.append(trimIndent(line, codeIndentLevel, tabWidth, indentWidth));
}
}
return buf.toString();
} catch (BadLocationException e) {
// can not happen
return code;
}
}
/**
* Change the indent of, possible muti-line, code range. The current indent is removed, a new indent added.
* The first line of the code will not be changed. (It is considered to have no indent as it might start in
* the middle of a line)
* @param source The code to change the indent of
* @param sourceIndentLevel The indent level of the code
* @param tabWidth The current tab width setting
* @param newIndent The new Indent string
* @return Returns the resulting text edits
*/
public static ReplaceEdit[] getChangeIndentEdits(String source, int sourceIndentLevel, int tabWidth, int indentWidth, String newIndent) {
ArrayList result= new ArrayList();
try {
ILineTracker tracker= new DefaultLineTracker();
tracker.set(source);
int nLines= tracker.getNumberOfLines();
if (nLines == 1)
return (ReplaceEdit[])result.toArray(new ReplaceEdit[result.size()]);
for (int i= 1; i < nLines; i++) {
IRegion region= tracker.getLineInformation(i);
int offset= region.getOffset();
String line= source.substring(offset, offset + region.getLength());
int length= Indents.computeIndentLength(line, sourceIndentLevel, tabWidth, indentWidth);
if (length >= 0) {
result.add(new ReplaceEdit(offset, length, newIndent));
} else {
length= Indents.computeIndentUnits(line, tabWidth, indentWidth);
result.add(new ReplaceEdit(offset, length, "")); //$NON-NLS-1$
}
}
} catch (BadLocationException cannotHappen) {
// can not happen
}
return (ReplaceEdit[])result.toArray(new ReplaceEdit[result.size()]);
}
/**
* Returns the tab width as configured in the given map.
* @param options The options to look at
* @return the tab width
*/
public static int getTabWidth(Map options) {
return parseIntValue(options, DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE, 4);
}
/**
* Returns the tab width as configured in the given map.
* @param options The options to look at
* @param tabWidth the tab width
* @return the indent width
*/
public static int getIndentWidth(Map options, int tabWidth) {
boolean isMixedMode= DefaultCodeFormatterConstants.MIXED.equals(options.get(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR));
if (isMixedMode) {
return parseIntValue(options, DefaultCodeFormatterConstants.FORMATTER_INDENTATION_SIZE, tabWidth);
}
return tabWidth;
}
private static int parseIntValue(Map options, String key, int def) {
try {
return Integer.parseInt((String) options.get(key));
} catch (NumberFormatException e) {
return def;
}
}
/**
* Change the indent of, possible muti-line, code range. The current indent is removed, a new indent added.
* The first line of the code will not be changed. (It is considered to have no indent as it might start in
* the middle of a line)
* @deprecated use the version specifying the indent width instead
*/
public static String changeIndent(String code, int codeIndentLevel, int tabWidth, String newIndent, String lineDelim) {
return changeIndent(code, codeIndentLevel, tabWidth, tabWidth, newIndent, lineDelim);
}
/**
* Returns the indent of the given string.
*
* @param line the text line
* @param tabWidth the width of the '\t' character.
* @deprecated use {@link #computeIndentUnits(String, int, int)} instead
*/
public static int computeIndent(String line, int tabWidth) {
return computeIndentUnits(line, tabWidth, tabWidth);
}
}