/*
* $Id$
*
* Copyright (c) 2004-2005 by the TeXlapse Team.
* 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
*/
package net.sourceforge.texlipse.editor;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.sourceforge.texlipse.TexlipsePlugin;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DocumentCommand;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.TextUtilities;
/**
* Offers general tools for different TexEditor features.
* Tools are used mainly to implement the word wrap and the indentation
* methods.
*
* @author Laura Takkinen
* @author Antti Pirinen
* @author Oskar Ojala
*/
public class TexEditorTools {
/**
* Matches some simple LaTeX -commands
*/
private static final Pattern simpleCommandPattern =
Pattern.compile("\\\\([a-zA-Z]+)\\s*\\{(.*?)\\}\\s*");
public TexEditorTools() {
}
/**
* Calculates the number of tabs ('\t') in given text.
* @param text Text where tabs are calculated.
* @return number of found tabs.
*/
public int numberOfTabs(String text) {
int count = 0;
char[] array = text.toCharArray();
if (array.length == 0) {
return count;
}
for (int i = 0; i < array.length; i++){
if (array[i] == '\t'){
count++;
}
}
return count;
}
/**
* Calculates the number of spaces (' ') in given text.
* @param text Text where spaces are calculated.
* @return number of found spaces.
*/
public int numberOfSpaces(String text) {
int count = 0;
char[] array = text.toCharArray();
if (array.length == 0) {
return count;
}
for (int i = 0; i < array.length; i++){
if (array[i] == ' '){
count++;
}
else{
break;
}
}
return count;
}
/**
* Returns the indentation of the given string. If the
* indentation contains tabular characters they are
* converted to the spaces.
* @param text source where to find the indentation
* @param tabWidth how many spaces one tabular character is
* @return the indentation of the line
*/
public String getIndentation(String text, int tabWidth) {
String indentation = "";
char[] array = text.toCharArray();
if (array.length == 0){
return indentation;
}
if (array[0] == ' ' || array[0] == '\t'){
int tabs = numberOfTabs(text);
int spaces = numberOfSpaces(text) - tabs + (tabs * tabWidth);
if (spaces > 0) {
for (int i = 0; i < spaces; i++) {
indentation += " ";
}
}
}
return indentation;
}
/**
* Returs indentation string without tabs. Method calculates
* number of tabs of text and converts them to spaces.
* @param document Document that contains the text.
* @param line Line for whitch the indentation is calculated.
* @param text Text that marks the beginning of the text in given line.
* @param tabWidth Number of spaces in tab.
* @return Indentation String.
*/
public String getIndentation(IDocument document, int line, String text,
int tabWidth) {
String indentation = "";
if (line == -1 || line >= document.getNumberOfLines()) {
return indentation;
}
try{
String lineText = document.get(document.getLineOffset(line), document.getLineLength(line));
int beginIndex = lineText.indexOf(text);
int tabs = numberOfTabs(lineText.substring(0, beginIndex));
int spaces = beginIndex - tabs + (tabs * tabWidth);
if (spaces > 0) {
for (int i = 0; i < spaces; i++) {
indentation += " ";
}
}
}catch (Exception e){
TexlipsePlugin.log("TexEditorTools:getIndentation", e);
}
return indentation;
}
/**
* Gets substring of the given text by removing the given prexif from the string.
* Removes also white spaces from the substring.
* For example: if text is "\begin {itemize}" and prefix is "\begin"
* return value is "{itemize}".
* @param text Text from where the substring is created.
* @param prefix Prefix that is removed from the text.
* @return Substring of the text.
* @throws IndexOutOfBoundsException
*/
public String getEndLine(String text, String prefix) throws
IndexOutOfBoundsException {
String endOfLine = text.substring(prefix.length());
return endOfLine.trim();
}
/**
* Gets environment string from the given text.
* @param text Text where the string is searched.
* @return Environment string (itemize, table...).
* If nothing is found, empty string is returned.
* @throws IndexOutOfBoundsException
*/
public String getEnvironment(String text) throws IndexOutOfBoundsException {
int begin = text.indexOf('{');
int end = text.indexOf('}');
//"{" has to be to the first character of the text
if (begin == 0 && end > begin){
return text.substring(begin + 1, end);
}
return "";
}
/**
* Finds matching \begin{environment} expression to the given \end{environment} line.
* @param document Document that contains line.
* @param line End line for which the matching beging line is searched.
* @param environment String that defines begin-end environment type (itemize, enumerate...)
* @return Returs line number of matching begin equation,
* if match does not found -1 is returned.
* @throws BadLocationException
*/
public int findMatchingBeginEquation(IDocument document, int line,
String environment) throws BadLocationException {
int startLine = line - 1;
int startOffset= document.getLineOffset(startLine);
int lineLength = document.getLineLength(startLine);
String lineText = document.get(startOffset, lineLength).trim();
boolean noMatch = true;
int beginCounter = 0;
int endCounter = 1; //one end has been detected earlier
while(noMatch){
if (lineText.startsWith("\\begin")){
String end = getEndLine(lineText, "\\begin");
if (getEnvironment(end).equals(environment)){
beginCounter++;
if (beginCounter == endCounter){
return startLine;
}
}
}
else if (lineText.startsWith("\\end")){
String end = getEndLine(lineText, "\\end");
if (getEnvironment(end).equals(environment)){
endCounter++;
}
}
if(startLine > 0){
startLine--;
startOffset = document.getLineOffset(startLine);
lineLength = document.getLineLength(startLine);
lineText = document.get(startOffset, lineLength).trim();
}
else{
noMatch = false;
}
}
return -1;
}
/**
* Returns the longest legal line delimiter.
* @param document IDocument
* @param command DocumentCommand
* @return the longest legal line delimiter
*/
public String getLineDelimiter(IDocument document, DocumentCommand command) {
String delimiter = "\n";
try {
delimiter = document.getLineDelimiter(0);
} catch (BadLocationException e) {
TexlipsePlugin.log("TexEditorTools.getLineDelimiter: ", e);
}
return delimiter == null ? "\n" : delimiter;
}
public String getLineDelimiter(IDocument document) {
return getLineDelimiter(document, null);
}
/**
* Returns a length of a line.
* @param document IDocument that contains the line.
* @param command DocumentCommand that determines the line.
* @param delim are line delimiters counted to the line length
* @return the line length
*/
public int getLineLength(IDocument document, DocumentCommand command,
boolean delim) {
return getLineLength(document, command, delim, 0);
}
/**
* Returns a length of a line.
* @param document IDocument that contains the line.
* @param command DocumentCommand that determines the line.
* @param delim are line delimiters counted to the line length
* @param target -1 = previous line, 0 = current line, 1 = next line etc...
* @return the line length
*/
public int getLineLength(IDocument document, DocumentCommand command,
boolean delim, int target) {
int line;
int length = 0;
try{
line = document.getLineOfOffset(command.offset) + target;
if (line < 0 || line >= document.getNumberOfLines()){
//line = document.getLineOfOffset(command.offset);
return 0;
}
length = document.getLineLength(line);
if (length == 0){
return 0;
}
if (!delim){
String txt = document.get(document.getLineOffset(line), document.getLineLength(line));
String[] del = document.getLegalLineDelimiters();
int cnt = TextUtilities.endsWith(del ,txt);
if (!delim && cnt > -1){
length = length - del[cnt].length();
}
}
}catch(BadLocationException e){
TexlipsePlugin.log("TexEditorTools.getLineLength:",e);
}
return length;
}
/**
* Returns a text String of the (line + <code>lineDif</code>).
* @param document IDocument that contains the line.
* @param command DocumentCommand that determines the line.
* @param delim are delimiters included
* @param lineDif 0 = current line, 1 = next line, -1 previous line etc...
* @return the text of the line.
*/
public String getStringAt(IDocument document,
DocumentCommand command, boolean delim, int lineDif) {
String line = "";
int lineBegin, lineLength;
try {
if (delim) {
lineLength = getLineLength(document, command, true, lineDif);
} else {
lineLength = getLineLength(document, command, false, lineDif);
}
if (lineLength > 0) {
lineBegin = document.getLineOffset(document
.getLineOfOffset(command.offset) + lineDif);
line = document.get(lineBegin, lineLength);
}
} catch (BadLocationException e) {
TexlipsePlugin.log("TexEditorTools.getStringAt", e);
}
return line;
}
/**
* Returns a text String of the line.
* @param d IDocument that contains the line.
* @param c DocumentCommand that determines the line.
* @param del Are delimiters included?
* @return The text of the current line (lineDif = 0).
*/
public String getStringAt(IDocument d, DocumentCommand c, boolean del) {
return getStringAt(d, c, del, 0);
}
/**
* Detects the position of the first white space character
* smaller than the limit.
* The first character at a row is 0 and last is lineText.length-1
* @param text to search
* @param limit the detected white space must be before this
* @return index of last white space character,
* returns -1 if not found.
*/
public int getLastWSPosition(String text, int limit) {
int index = -1;
if (text.length() >= limit && limit > -1) {
String temp = text.substring(0, limit); // TODO limit+1?
int lastSpace = temp.lastIndexOf(' ');
int lastTab = temp.lastIndexOf('\t');
index = (lastSpace > lastTab ? lastSpace : lastTab);
}
return index;
}
/**
* Detects the position of the first white space character
* larger than the limit.
* The first character at a row is 0 and last is lineText.length-1
* @param text to search
* @param limit the detected white space is the first white space
* after this
* @return index of first white space character,
* returns -1 if not found.
*/
public int getFirstWSPosition(String text, int limit) {
int index = -1;
if (text.length() > limit && limit > -1) {
String temp = text.substring(limit + 1);
int firstSpace = temp.indexOf(' ');
int firstTab = temp.indexOf('\t');
if (firstSpace == -1 && firstTab != -1) {
index = firstTab + limit + 1;
} else if (firstSpace != -1 && firstTab == -1) {
index = firstSpace + limit + 1;
} else if (firstSpace > -1 && firstTab > -1) {
index = (firstSpace < firstTab ? firstSpace : firstTab) + limit + 1;
}
}
return index;
}
/**
* Trims the beginning of the given text.
* @param text String that will be trimmed.
* @return trimmed String.
*/
public String trimBegin(String text) {
char[] array = text.toCharArray();
int i = 0;
for (; i < array.length; i++){
if(array[i] !=' ' && array[i] !='\t') break;
}
return text.substring(i);
}
/**
* Trims the end of the given text.
* @param text String that will be trimmed.
* @return trimmed String.
*/
public String trimEnd(String text) {
char[] array = text.toCharArray();
int i = array.length-1;
for (; i >= 0; i--){
if(array[i] !=' ' && array[i] !='\t') break;
}
return text.substring(0, i+1);
}
/**
* Checks if the target text begins with a LaTeX command.
* @param text source string
* @return <code>true</code> if the line contains the latex command word,
* <code>false</code> otherwise
*/
public boolean isLineCommandLine(String text) {
String txt = text.trim();
Matcher m = simpleCommandPattern.matcher(txt);
if (m.matches()) {
return true;
}
return false;
}
/**
* Checks if the target txt is a comment line
* @param text source text
* @return <code>true</code> if line starts with %-character,
* <code>false</code> otherwise
*/
public boolean isLineCommentLine(String text) {
return text.trim().startsWith("%");
}
/**
* Checks is the line begins with \item keyword
* @param text string to test
* @return <code>true</code> if the line contains the item key word,
* <code>false</code> otherwise
*/
public boolean isLineItemLine(String text){
return text.trim().startsWith("\\item");
}
/**
* This method will return the starting index of first
* comment on the given line or -1 if non is found.
*
* This method looks for the first occurrence of an unescaped %
*
* No special treatment of newlines is done.
*
* @param line The line on which to look for a comment.
*
* @return the index of the first % which marks the beginning of a comment
* or -1 if there is no comment on the given line.
*/
public int getIndexOfComment(String line) {
int p = 0;
int n = line.length();
while (p < n) {
char c = line.charAt(p);
if (c == '%') {
return p;
} else if (c == '\\') {
p++; // Ignore next character
}
p++;
}
return -1; // not found
}
// Oskar's additions
/**
* Returns the indentation of the given string taking
* into account if the string starts with a comment.
* The comment character is included in the output.
*
* @param text source where to find the indentation
* @return The indentation of the line including the comment
*/
public String getIndentationWithComment(String text) {
StringBuffer indentation = new StringBuffer();
char[] array = text.toCharArray();
if (array.length == 0) {
return indentation.toString();
}
int i = 0;
while (i < array.length
&& (array[i] == ' ' || array[i] == '\t')) {
indentation.append(array[i]);
i++;
}
if (i < array.length && array[i] == '%') {
indentation.append("% ");
}
return indentation.toString();
}
/**
* Returns the indentation of the given string but keeping tabs.
*
* @param text source where to find the indentation
* @return The indentation of the line
*/
public String getIndentation(String text) {
StringBuffer indentation = new StringBuffer();
char[] array = text.toCharArray();
int i = 0;
while (i < array.length
&& (array[i] == ' ' || array[i] == '\t')) {
indentation.append(array[i]);
i++;
}
return indentation.toString();
}
public String wrapWordString(String input, String indent, int width, String delim) {
String[] words = input.split("\\s");
if (input.length() == 0 || words.length == 0) {
return "";
}
StringBuffer sbout = new StringBuffer(indent);
sbout.append(words[0]);
int currLength = indent.length() + words[0].length();
for (int j = 1; j < words.length; j++) {
// Check whether the next word still fits on the current line
if (currLength + 1 + words[j].length() <= width) {
sbout.append(" ");
sbout.append(words[j]);
currLength += 1 + words[j].length();
} else {
sbout.append(delim);
sbout.append(indent);
sbout.append(words[j]);
currLength = indent.length() + words[j].length();
}
}
sbout.append(delim);
return sbout.toString();
}
public String[] getEnvCommandArg(String text) {
String txt = text.trim();
Matcher m = simpleCommandPattern.matcher(txt);
while (m.find()) {
if ("begin".equals(m.group(1)) || "end".equals(m.group(1))) {
return new String[] {m.group(1), m.group(2)};
}
}
return new String[] {"", ""};
}
public String getNewlinesAtEnd(String text, String delim) {
StringBuffer sb = new StringBuffer();
for (int i = text.length() - delim.length(); i >= 0; i -= delim.length()) {
if (text.regionMatches(i, delim, 0, delim.length())) {
sb.append(delim);
} else {
break;
}
}
return sb.toString();
}
}