blob: df8a971068714e1b9ad28d153c359896a80a066a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2013 John-Mason P. Shackelford and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* John-Mason P. Shackelford - initial API and implementation
* IBM Corporation - bug fixes
*******************************************************************************/
package org.eclipse.ant.internal.ui.editor.formatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.eclipse.ant.internal.core.IAntCoreConstants;
import org.eclipse.ant.internal.ui.editor.templates.AntContext;
import org.eclipse.ant.internal.ui.editor.text.AntDocumentSetupParticipant;
import org.eclipse.ant.internal.ui.editor.text.AntEditorPartitionScanner;
import org.eclipse.ant.internal.ui.model.AntElementNode;
import org.eclipse.ant.internal.ui.model.AntProjectNode;
import org.eclipse.ant.internal.ui.model.IAntModel;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.BadPositionCategoryException;
import org.eclipse.jface.text.DefaultPositionUpdater;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.formatter.MultiPassContentFormatter;
import org.eclipse.jface.text.templates.TemplateBuffer;
import org.eclipse.jface.text.templates.TemplateVariable;
/**
* Utility class for using the ant code formatter in contexts where an IDocument containing the text to format is not readily available.
*/
public class XmlFormatter {
private static final String POS_CATEGORY = "tempAntFormatterCategory"; //$NON-NLS-1$
/**
* Format the text using the ant code formatter.
*
* @param text
* The text to format. Must be a non-null value.
* @param prefs
* Preferences to use for this format operation. If null, the preferences currently set in the plug-in's preferences store are used.
* @return The formatted text.
*/
public static String format(String text, FormattingPreferences prefs) {
return format(text, prefs, -1);
}
private static String format(String text, FormattingPreferences prefs, int indent) {
Assert.isNotNull(text);
FormattingPreferences applyPrefs;
if (prefs == null) {
applyPrefs = new FormattingPreferences();
} else {
applyPrefs = prefs;
}
IDocument doc = new Document();
doc.set(text);
new AntDocumentSetupParticipant().setup(doc);
format(applyPrefs, doc, indent);
return doc.get();
}
private static void format(FormattingPreferences prefs, IDocument doc, int indent) {
MultiPassContentFormatter formatter = new MultiPassContentFormatter(AntDocumentSetupParticipant.ANT_PARTITIONING, IDocument.DEFAULT_CONTENT_TYPE);
formatter.setMasterStrategy(new XmlDocumentFormattingStrategy(prefs, indent));
formatter.setSlaveStrategy(new XmlElementFormattingStrategy(prefs), AntEditorPartitionScanner.XML_TAG);
formatter.format(doc, new Region(0, doc.getLength()));
}
/**
* Format the text using the ant code formatter using the preferences settings in the plug-in preference store.
*
* @param text
* The text to format. Must be a non-null value.
* @return The formatted text.
*/
public static String format(String text) {
return format(text, null);
}
public static void format(TemplateBuffer templateBuffer, AntContext antContext, FormattingPreferences prefs) {
String templateString = templateBuffer.getString();
IDocument fullDocument = new Document(antContext.getDocument().get());
int completionOffset = antContext.getCompletionOffset();
try {
// trim any starting whitespace
IRegion lineRegion = fullDocument.getLineInformationOfOffset(completionOffset);
String lineString = fullDocument.get(lineRegion.getOffset(), lineRegion.getLength());
lineString = trimBegin(lineString);
fullDocument.replace(lineRegion.getOffset(), lineRegion.getLength(), lineString);
}
catch (BadLocationException e1) {
return;
}
TemplateVariable[] variables = templateBuffer.getVariables();
int[] offsets = variablesToOffsets(variables, completionOffset);
IDocument origTemplateDoc = new Document(fullDocument.get());
try {
origTemplateDoc.replace(completionOffset, antContext.getCompletionLength(), templateString);
}
catch (BadLocationException e) {
return; // don't format if the document has changed
}
IDocument templateDocument = createDocument(origTemplateDoc.get(), createPositions(offsets));
String leadingText = getLeadingText(fullDocument, antContext.getAntModel(), completionOffset);
String newTemplateString = leadingText + templateString;
int indent = XmlDocumentFormatter.computeIndent(leadingText, prefs.getTabWidth());
newTemplateString = format(newTemplateString, prefs, indent);
try {
templateDocument.replace(completionOffset, templateString.length(), newTemplateString);
}
catch (BadLocationException e) {
return;
}
Position[] positions = null;
try {
positions = templateDocument.getPositions(POS_CATEGORY);
}
catch (BadPositionCategoryException e2) {
// do nothing
}
// offsetsToVariables(offsets, variables, completionOffset);
positionsToVariables(positions, variables, completionOffset);
templateBuffer.setContent(newTemplateString, variables);
}
private static void positionsToVariables(Position[] positions, TemplateVariable[] variables, int start) {
for (int i = 0; i != variables.length; i++) {
TemplateVariable variable = variables[i];
int[] offsets = new int[variable.getOffsets().length];
for (int j = 0; j != offsets.length; j++) {
offsets[j] = positions[j].getOffset() - start;
}
variable.setOffsets(offsets);
}
}
private static Document createDocument(String string, Position[] positions) throws IllegalArgumentException {
Document doc = new Document(string);
try {
if (positions != null) {
doc.addPositionCategory(POS_CATEGORY);
doc.addPositionUpdater(new DefaultPositionUpdater(POS_CATEGORY) {
@Override
protected boolean notDeleted() {
if (fOffset < fPosition.offset && (fPosition.offset + fPosition.length < fOffset + fLength)) {
fPosition.offset = fOffset + fLength; // deleted positions: set to end of remove
return false;
}
return true;
}
});
for (int i = 0; i < positions.length; i++) {
try {
doc.addPosition(POS_CATEGORY, positions[i]);
}
catch (BadLocationException e) {
throw new IllegalArgumentException("Position outside of string. offset: " + positions[i].offset + ", length: " //$NON-NLS-1$//$NON-NLS-2$
+ positions[i].length + ", string size: " + string.length(), e); //$NON-NLS-1$
}
}
}
}
catch (BadPositionCategoryException cannotHappen) {
// can not happen: category is correctly set up
}
return doc;
}
public static String trimBegin(String toBeTrimmed) {
int i = 0;
while ((i != toBeTrimmed.length()) && Character.isWhitespace(toBeTrimmed.charAt(i))) {
i++;
}
return toBeTrimmed.substring(i);
}
private static int[] variablesToOffsets(TemplateVariable[] variables, int start) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i != variables.length; i++) {
int[] offsets = variables[i].getOffsets();
for (int j = 0; j != offsets.length; j++) {
list.add(Integer.valueOf(offsets[j]));
}
}
int[] offsets = new int[list.size()];
for (int i = 0; i != offsets.length; i++) {
offsets[i] = list.get(i).intValue() + start;
}
Arrays.sort(offsets);
return offsets;
}
/**
* Returns the indentation level at the position of code completion.
*/
private static String getLeadingText(IDocument document, IAntModel model, int completionOffset) {
AntProjectNode project = model.getProjectNode(false);
if (project == null) {
return IAntCoreConstants.EMPTY_STRING;
}
AntElementNode node = project.getNode(completionOffset);// - fAccumulatedChange);
if (node == null) {
return IAntCoreConstants.EMPTY_STRING;
}
StringBuffer buf = new StringBuffer();
buf.append(XmlDocumentFormatter.getLeadingWhitespace(node.getOffset(), document));
buf.append(XmlDocumentFormatter.createIndent());
return buf.toString();
}
private static Position[] createPositions(int[] positions) {
Position[] p = null;
if (positions != null) {
p = new Position[positions.length];
for (int i = 0; i < positions.length; i++) {
p[i] = new Position(positions[i], 0);
}
}
return p;
}
}