/*
* $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.Arrays;
import net.sourceforge.texlipse.TexlipsePlugin;
import net.sourceforge.texlipse.editor.partitioner.FastLaTeXPartitionScanner;
import net.sourceforge.texlipse.properties.TexlipseProperties;
import net.sourceforge.texlipse.texparser.LatexParserUtils;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DefaultIndentLineAutoEditStrategy;
import org.eclipse.jface.text.DocumentCommand;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension3;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
/**
* Defines indentation strategy.
*
* @author Laura Takkinen
* @author Antti Pirinen
* @author Oskar Ojala
* @author Boris von Loesch
*/
public class TexAutoIndentStrategy extends DefaultIndentLineAutoEditStrategy {
private String indentationString = "";
private String[] indentationItems;
private int lineLength;
private static boolean hardWrap = false;
private boolean indent;
private HardLineWrap hlw;
private boolean autoItem = true;
private boolean itemSetted = false;
private int itemAtLine = 0;
final private IPreferenceStore fPreferenceStore;
/**
* Creates new TexAutoIndentStrategy.
*
* @param store
*/
public TexAutoIndentStrategy() {
fPreferenceStore = TexlipsePlugin.getDefault().getPreferenceStore();
fPreferenceStore.addPropertyChangeListener(new IPropertyChangeListener() {
public void propertyChange(PropertyChangeEvent event) {
String ev = event.getProperty();
if (TexlipseProperties.WORDWRAP_LENGTH.equals(ev)) {
lineLength = fPreferenceStore.getInt(TexlipseProperties.WORDWRAP_LENGTH);
}
else if (TexlipseProperties.TEX_ITEM_COMPLETION.equals(ev)) {
autoItem = fPreferenceStore.getBoolean(TexlipseProperties.TEX_ITEM_COMPLETION);
}
else if (TexlipseProperties.INDENTATION_LEVEL.equals(ev)
|| TexlipseProperties.INDENTATION_TABS.equals(ev)
|| TexlipseProperties.INDENTATION.equals(ev)
|| TexlipseProperties.INDENTATION_ENVS.equals(ev)) {
setIndetationPreferenceInfo();
}
};
});
this.hlw = new HardLineWrap();
setIndetationPreferenceInfo();
}
/**
* Returns a default indentation string,
* that is created according to the preferences
* @return string consists of tabs or spaces
*/
public static String getIndentationString() {
String indentationString = "";
if (TexlipsePlugin.getDefault().getPreferenceStore().getBoolean(TexlipseProperties.INDENTATION_TABS)) {
indentationString = "\t";
} else {
int indentationLevel = TexlipsePlugin.getDefault().getPreferenceStore().getInt(TexlipseProperties.INDENTATION_LEVEL);
for (int i = 0; i < indentationLevel; i++) {
indentationString += " ";
}
}
return indentationString;
}
/**
* Initializes indentation information from preferences
*/
private void setIndetationPreferenceInfo() {
indentationItems = TexlipsePlugin.getPreferenceArray(TexlipseProperties.INDENTATION_ENVS);
Arrays.sort(indentationItems);
//tabWidth = editorPreferenceStore.getInt(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_TAB_WIDTH);
indentationString = getIndentationString();
indent = Boolean.parseBoolean(TexlipsePlugin.getPreference(TexlipseProperties.INDENTATION));
lineLength = fPreferenceStore.getInt(TexlipseProperties.WORDWRAP_LENGTH);
autoItem = fPreferenceStore.getBoolean(TexlipseProperties.TEX_ITEM_COMPLETION);
}
/*
* (non-Javadoc) Method declared on IAutoIndentStrategy
*/
public void customizeDocumentCommand(IDocument document, DocumentCommand command) {
if (this.indent) {
if (itemSetted && autoItem && command.length == 0 && command.text != null
&& TextUtilities.endsWith(document.getLegalLineDelimiters(), command.text) != -1) {
dropItem(document, command);
} else if (command.length == 0 && command.text != null
&& TextUtilities.endsWith(document.getLegalLineDelimiters(), command.text) != -1) {
smartIndentAfterNewLine(document, command);
} else if ("}".equals(command.text)) {
smartIndentAfterBrace(document, command);
} else {
itemSetted = false;
}
}
if (TexAutoIndentStrategy.hardWrap && command.length == 0 && command.text != null) {
try {
final String contentType = ((IDocumentExtension3) document).getContentType(
TexEditor.TEX_PARTITIONING, command.offset, true);
// Do not wrap in verbatim partitions
if (!FastLaTeXPartitionScanner.TEX_VERBATIM.equals(contentType)) {
hlw.doWrapB(document, command, lineLength);
}
}
catch (Exception e) {
// If we cannot retrieve the current content type, do not break lines
}
}
}
/**
* Decides if a "\begin{...}" needs a "\end{...}"
* @param environment Name of the environment (...)
* @param document The document as String
* @param coffset The starting offset (just at the beginning of
* the "\begin{...}"
* @return true, if it needs an end, else false
*/
public static boolean needsEnd(String environment, String docString, int coffset) {
int counter = 1;
int offset = coffset;
while (counter > 0) {
IRegion end = LatexParserUtils.findEndEnvironment(docString, environment, offset + 5);
if (end == null) {
return true;
}
IRegion start = LatexParserUtils.findBeginEnvironment(docString, environment, offset + 7);
if (start == null) {
counter--;
offset = end.getOffset();
} else if (end.getOffset() > start.getOffset()) {
counter++;
offset = start.getOffset();
} else {
counter--;
offset = end.getOffset();
}
}
return false;
}
/**
* Returns a string with the whitespaces (spaces and tabs) that are at the beginning
* of line
* @param line
* @return
*/
public static String getIndentation(String line) {
int offset = 0;
while (offset < line.length() && (line.charAt(offset) == ' ' || line.charAt(offset) == '\t')) {
offset++;
}
return line.substring(0, offset);
}
/**
* Performs indentation after new line is detected.
*
* @param document Document where new line is detected.
* @param command Command that represent the change of the document (new
* line).
*/
private void smartIndentAfterNewLine(IDocument document, DocumentCommand command) {
try {
itemSetted = false;
int commandOffset = command.offset;
int line = document.getLineOfOffset(commandOffset);
int lineOffset = document.getLineOffset(line);
String startLine = document.get(lineOffset, commandOffset - lineOffset);
//this is save
String lineDelimiter = document.getLegalLineDelimiters()
[TextUtilities.endsWith(document.getLegalLineDelimiters(), command.text)];
int beginIndex;
if ((beginIndex = LatexParserUtils.findCommand(startLine, "\\begin", 0)) != -1) {
// test if line contains \begin and search the environment (itemize,
// table...)
IRegion r = LatexParserUtils.getCommandArgument(startLine, beginIndex);
if (r == null){
//No environment found
super.customizeDocumentCommand(document, command);
return;
}
String envName = startLine.substring(r.getOffset(), r.getOffset()+r.getLength());
StringBuilder buf = new StringBuilder(command.text);
// get indentation of \begin
/* String prevIndentation = this.tools.getIndentation(document, line,
"\\begin", this.tabWidth); // NEW*/
String prevIndentation = getIndentation(startLine);
if (Arrays.binarySearch(this.indentationItems, envName) >= 0) {
buf.append(prevIndentation);
buf.append(this.indentationString);
} else {
buf.append(prevIndentation);
}
if (autoItem && (envName.equals("itemize") || envName.equals("enumerate"))) {
buf.append("\\item ");
itemSetted = true;
itemAtLine = document.getLineOfOffset(command.offset);
} else if (autoItem && envName.equals("description")) {
buf.append("\\item[]");
itemSetted = true;
itemAtLine = document.getLineOfOffset(command.offset);
}
command.caretOffset = command.offset + buf.length();
command.shiftsCaret = false;
if (autoItem && envName.equals("description")) {
command.caretOffset--;
}
/*
* looks for the \begin-statement and inserts
* an equivalent \end-statement (respects \begin-indentation)
*/
if (needsEnd(envName, document.get(), lineOffset)){
buf.append(lineDelimiter);
buf.append(prevIndentation);
buf.append("\\end{" + envName + "}");
}
command.text = buf.toString();
} else {
if (autoItem && !itemInserted(document, command)) {
super.customizeDocumentCommand(document, command);
} else {
super.customizeDocumentCommand(document, command);
}
}
} catch (BadLocationException e) {
TexlipsePlugin.log("TexAutoIndentStrategy:SmartIndentAfterNewLine", e);
}
}
/**
* Removes indentation if \end{...} is detected. We assume that
* command.text is the closing brace '}'
*
* @param document
* Document where new line is detected.
* @param command
* Command that represent the change of the document (here command text is "}").
*/
private void smartIndentAfterBrace(IDocument document, DocumentCommand command) {
try {
int commandOffset = command.offset;
int line = document.getLineOfOffset(commandOffset);
int lineOffset = document.getLineOffset(line);
int lineLength = document.getLineLength(line);
// the original line text
String lineText = document.get(lineOffset, lineLength);
// modified linetext
String text = lineText.trim().concat(command.text);
if (text.startsWith("\\end")) {
IRegion r = LatexParserUtils.getCommandArgument(text, 0);
//String envName = "";
if (r == null) {
super.customizeDocumentCommand(document, command);
return;
}
String envName = text.substring(r.getOffset(), r.getOffset() + r.getLength());
String docText = document.get();
IRegion rBegin = LatexParserUtils.findMatchingBeginEnvironment(docText, envName, lineOffset);
int beginLineNr = document.getLineOfOffset(rBegin.getOffset());
int beginLineLength = document.getLineLength(beginLineNr);
int beginLineStart = document.getLineOffset(beginLineNr);
String beginLine = document.get(beginLineStart, beginLineLength);
String beginInd = getIndentation(beginLine);
command.text = beginInd + text;
command.length = commandOffset - lineOffset;
command.offset = lineOffset;
} else {
super.customizeDocumentCommand(document, command);
}
} catch (BadLocationException e) {
TexlipsePlugin.log("TexAutoIndentStrategy:SmartIndentAfterBracket", e);
}
}
/**
* Erases the \item -string from a line
*
* @param d
* @param c
*/
private void dropItem(IDocument d, DocumentCommand c) {
try {
if (itemSetted && itemAtLine == (d.getLineOfOffset(c.offset) - 1)) {
IRegion r = d.getLineInformationOfOffset(c.offset);
String line = d.get(r.getOffset(), r.getLength());
if ("\\item".equals(line.trim()) || "\\item[]".equals(line.trim())) {
c.shiftsCaret = false;
c.length = line.length();
c.offset = r.getOffset();
c.text = getIndentation(line);
c.caretOffset = c.offset + c.text.length();
}
}
} catch (BadLocationException e) {
TexlipsePlugin.log("TexAutoIndentStrategy:dropItem", e);
}
itemSetted = false;
}
/**
* Inserts an \item or an \item[] string. Works ONLY it \item is found at
* the beginning of a preceeding line
*
* @param d
* @param c
* @return <code>true</code> if item was inserted, <code>false</code>
* otherwise
*/
private boolean itemInserted(IDocument d, DocumentCommand c) {
itemSetted = false;
try {
int lineNr = d.getLineOfOffset(c.offset);
int lineEnd = d.getLineOffset(lineNr) + d.getLineLength(lineNr);
//Test if there is no text behind the cursor in the line
if (c.offset < lineEnd - 1) return false;
int currentLineNr = lineNr;
String indentation = null;
while (lineNr >= 0) {
IRegion r = d.getLineInformation(lineNr);
String prevLine = d.get(r.getOffset(), r.getLength());
if (indentation == null) indentation = getIndentation(prevLine);
if (prevLine.trim().startsWith("\\item")) {
StringBuilder buf = new StringBuilder(c.text);
buf.append(indentation);
if (prevLine.trim().startsWith("\\item[")) {
c.shiftsCaret = false;
c.caretOffset = c.offset
+ buf.length() + 5
+ c.text.length();
buf.append("\\item[]");
} else {
buf.append("\\item ");
}
itemSetted = true;
itemAtLine = currentLineNr;
c.text = buf.toString();
return true;
}
if (prevLine.trim().startsWith("\\begin") || prevLine.trim().startsWith("\\end"))
return false;
lineNr--;
}
} catch (BadLocationException e) {
//Ignore
}
return false;
}
/**
* @param hardWrap The hardWrap to set.
*/
public static void setHardWrap(boolean hardWrap) {
TexAutoIndentStrategy.hardWrap = hardWrap;
}
}