| /******************************************************************************* |
| * Copyright (c) 2004 John-Mason P. Shackelford and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Common Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/cpl-v10.html |
| * |
| * Contributors: |
| * John-Mason P. Shackelford - initial API and implementation |
| *******************************************************************************/ |
| 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.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.jface.text.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) { |
| } |
| //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) { |
| 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: " + positions[i].length + ", string size: " + string.length()); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ |
| } |
| } |
| } |
| } 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 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(new Integer(offsets[j])); |
| } |
| } |
| |
| int[] offsets= new int[list.size()]; |
| for (int i= 0; i != offsets.length; i++) { |
| offsets[i]= ((Integer) 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 ""; //$NON-NLS-1$ |
| } |
| AntElementNode node= project.getNode(completionOffset);// - fAccumulatedChange); |
| if (node == null) { |
| return ""; //$NON-NLS-1$ |
| } |
| |
| 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; |
| } |
| } |