Index: .project =================================================================== retrieving revision 1.17 diff -u -r1.17 .project --- .project 9 Jan 2004 23:40:25 -0000 1.17 +++ .project 29 Jan 2004 08:47:30 -0000 @@ -10,7 +10,6 @@ org.eclipse.core.variables org.eclipse.debug.core org.eclipse.debug.ui - org.eclipse.jdt.debug.ui org.eclipse.jdt.launching org.eclipse.jface.text org.eclipse.ui Index: Ant Editor/org/eclipse/ant/internal/ui/editor/AntEditor.java =================================================================== retrieving revision 1.14 diff -u -r1.14 AntEditor.java --- Ant Editor/org/eclipse/ant/internal/ui/editor/AntEditor.java 23 Jan 2004 00:43:36 -0000 1.14 +++ Ant Editor/org/eclipse/ant/internal/ui/editor/AntEditor.java 29 Jan 2004 08:47:34 -0000 @@ -16,6 +16,8 @@ import java.util.ResourceBundle; +import org.eclipse.ant.internal.ui.editor.model.AntElementNode; +import org.eclipse.ant.internal.ui.editor.model.IAntEditorConstants; import org.eclipse.ant.internal.ui.editor.outline.AntEditorContentOutlinePage; import org.eclipse.ant.internal.ui.editor.outline.AntModel; import org.eclipse.ant.internal.ui.editor.outline.XMLCore; @@ -26,6 +28,7 @@ import org.eclipse.ant.internal.ui.model.AntUIPlugin; import org.eclipse.ant.internal.ui.model.IAntUIHelpContextIds; import org.eclipse.core.runtime.CoreException; +import org.eclipse.jface.action.IAction; import org.eclipse.jface.text.source.IAnnotationAccess; import org.eclipse.jface.text.source.IOverviewRuler; import org.eclipse.jface.text.source.ISourceViewer; @@ -46,6 +49,7 @@ import org.eclipse.ui.texteditor.IDocumentProvider; import org.eclipse.ui.texteditor.IEditorStatusLine; import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds; +import org.eclipse.ui.texteditor.TextOperationAction; import org.eclipse.ui.views.contentoutline.IContentOutlinePage; /** @@ -114,11 +118,14 @@ protected void createActions() { super.createActions(); - ContentAssistAction action = new ContentAssistAction(ResourceBundle.getBundle("org.eclipse.ant.internal.ui.editor.AntEditorMessages"), "ContentAssistProposal.", this); //$NON-NLS-1$ //$NON-NLS-2$ - + ResourceBundle bundle = ResourceBundle.getBundle("org.eclipse.ant.internal.ui.editor.AntEditorMessages"); //$NON-NLS-1$ + IAction action = new ContentAssistAction(bundle, "ContentAssistProposal.", this); //$NON-NLS-1$ // This action definition is associated with the accelerator Ctrl+Space action.setActionDefinitionId(ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS); setAction("ContentAssistProposal", action); //$NON-NLS-1$ + + action = new TextOperationAction(bundle,"ContentFormatProposal.",this,ISourceViewer.FORMAT); //$NON-NLS-1$ + setAction("ContentFormatProposal", action); //$NON-NLS-1$ } /* @@ -218,7 +225,7 @@ if (!moveCursor) { return; } - + if (offset > -1 && length > 0) { sourceViewer.revealRange(offset, length); // Selected region begins one index after offset @@ -281,7 +288,7 @@ } } } - + /** * Returns the Ant model for the current editor input of this editor. * @return the Ant model for this editor or null Index: Ant Editor/org/eclipse/ant/internal/ui/editor/AntEditorActionContributor.java =================================================================== retrieving revision 1.1 diff -u -r1.1 AntEditorActionContributor.java --- Ant Editor/org/eclipse/ant/internal/ui/editor/AntEditorActionContributor.java 29 Aug 2003 18:16:32 -0000 1.1 +++ Ant Editor/org/eclipse/ant/internal/ui/editor/AntEditorActionContributor.java 29 Jan 2004 08:47:34 -0000 @@ -17,7 +17,6 @@ import java.util.ResourceBundle; import org.eclipse.jface.action.IMenuManager; -import org.eclipse.ui.IActionBars; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IWorkbenchActionConstants; import org.eclipse.ui.editors.text.TextEditorActionContributor; @@ -32,56 +31,54 @@ public class AntEditorActionContributor extends TextEditorActionContributor { protected RetargetTextEditorAction fContentAssistProposal; -// protected RetargetTextEditorAction fContentAssistTip; - + protected RetargetTextEditorAction fContentFormatProposal; + /** * Default constructor. */ public AntEditorActionContributor() { super(); - fContentAssistProposal= new RetargetTextEditorAction(ResourceBundle.getBundle("org.eclipse.ant.internal.ui.editor.AntEditorMessages"), "ContentAssistProposal."); //$NON-NLS-1$ //$NON-NLS-2$ -// fContentAssistTip= new RetargetTextEditor(ResourceBundle.getBundle("org.eclipse.ant.internal.ui.editor.AntEditorMessages"), "ContentAssistTip."); //$NON-NLS-1$ + ResourceBundle bundle = ResourceBundle.getBundle("org.eclipse.ant.internal.ui.editor.AntEditorMessages"); //$NON-NLS-1$ + fContentAssistProposal = new RetargetTextEditorAction(bundle, "ContentAssistProposal."); //$NON-NLS-1$ + fContentFormatProposal = new RetargetTextEditorAction(bundle, "ContentFormatProposal."); //$NON-NLS-1$ } - - private void doSetActiveEditor(IEditorPart part) { - super.setActiveEditor(part); - - ITextEditor editor= null; - if (part instanceof ITextEditor) - editor= (ITextEditor) part; - - fContentAssistProposal.setAction(getAction(editor, "ContentAssistProposal")); //$NON-NLS-1$ -// fContentAssistTip.setAction(getAction(editor, "ContentAssistTip")); //$NON-NLS-1$ - } - - /* - * @see IEditorActionBarContributor#init(IActionBars) - */ - public void init(IActionBars bars) { - super.init(bars); - - IMenuManager menuManager= bars.getMenuManager(); - IMenuManager editMenu= menuManager.findMenuUsingPath(IWorkbenchActionConstants.M_EDIT); - if (editMenu != null) { - editMenu.add(new org.eclipse.jface.action.Separator()); - editMenu.add(fContentAssistProposal); -// editMenu.add(fContentAssistTip); - } - - } - + /* * @see IEditorActionBarContributor#setActiveEditor(IEditorPart) */ public void setActiveEditor(IEditorPart part) { - doSetActiveEditor(part); + super.setActiveEditor(part); + + ITextEditor editor= null; + if (part instanceof ITextEditor) + editor= (ITextEditor) part; + + fContentAssistProposal.setAction(getAction(editor, "ContentAssistProposal")); //$NON-NLS-1$ + fContentFormatProposal.setAction(getAction(editor, "ContentFormatProposal")); //$NON-NLS-1$ + + } + + /* (non-Javadoc) + * @see org.eclipse.ui.part.EditorActionBarContributor#contributeToMenu(org.eclipse.jface.action.IMenuManager) + */ + public void contributeToMenu(IMenuManager menu) { + super.contributeToMenu(menu); + + IMenuManager editMenu= menu.findMenuUsingPath(IWorkbenchActionConstants.M_EDIT); + if (editMenu != null) { + editMenu.add(new org.eclipse.jface.action.Separator()); + editMenu.add(fContentAssistProposal); + editMenu.add(fContentFormatProposal); + } } /* * @see IEditorActionBarContributor#dispose() */ public void dispose() { - doSetActiveEditor(null); + setActiveEditor(null); super.dispose(); } + + } Index: Ant Editor/org/eclipse/ant/internal/ui/editor/AntEditorMessages.properties =================================================================== retrieving revision 1.3 diff -u -r1.3 AntEditorMessages.properties --- Ant Editor/org/eclipse/ant/internal/ui/editor/AntEditorMessages.properties 22 Jan 2004 13:32:07 -0000 1.3 +++ Ant Editor/org/eclipse/ant/internal/ui/editor/AntEditorMessages.properties 29 Jan 2004 08:47:34 -0000 @@ -14,6 +14,11 @@ ContentAssistProposal.image= ContentAssistProposal.description=Content Assist +ContentFormatProposal.label=Format XML +ContentFormatProposal.tooltip=Format build file source +ContentFormatProposal.image= +ContentFormatProposal.description=Format build file source + AntEditorCompletionProcessor.Required___4=Required: AntEditorCompletionProcessor.28=No attribute completions available AntEditorCompletionProcessor.29=No task completions available Index: Ant Editor/org/eclipse/ant/internal/ui/editor/AntEditorSourceViewerConfiguration.java =================================================================== retrieving revision 1.5 diff -u -r1.5 AntEditorSourceViewerConfiguration.java --- Ant Editor/org/eclipse/ant/internal/ui/editor/AntEditorSourceViewerConfiguration.java 23 Jan 2004 00:40:34 -0000 1.5 +++ Ant Editor/org/eclipse/ant/internal/ui/editor/AntEditorSourceViewerConfiguration.java 29 Jan 2004 08:47:34 -0000 @@ -15,6 +15,8 @@ package org.eclipse.ant.internal.ui.editor; import org.eclipse.ant.internal.ui.editor.derived.HTMLTextPresenter; +import org.eclipse.ant.internal.ui.editor.format.XmlDocumentFormattingStrategy; +import org.eclipse.ant.internal.ui.editor.format.XmlElementFormattingStrategy; import org.eclipse.ant.internal.ui.editor.text.AntEditorPartitionScanner; import org.eclipse.ant.internal.ui.editor.text.AntEditorProcInstrScanner; import org.eclipse.ant.internal.ui.editor.text.AntEditorTagScanner; @@ -37,6 +39,10 @@ import org.eclipse.jface.text.TextAttribute; import org.eclipse.jface.text.contentassist.ContentAssistant; import org.eclipse.jface.text.contentassist.IContentAssistant; +import org.eclipse.jface.text.formatter.ContentFormatter2; +import org.eclipse.jface.text.formatter.ContentFormatter3; +import org.eclipse.jface.text.formatter.IContentFormatter; +import org.eclipse.jface.text.formatter.IFormattingStrategy; import org.eclipse.jface.text.presentation.IPresentationReconciler; import org.eclipse.jface.text.presentation.PresentationReconciler; import org.eclipse.jface.text.reconciler.IReconciler; @@ -257,4 +263,21 @@ } } } + /* (non-Javadoc) + * @see org.eclipse.jface.text.source.SourceViewerConfiguration#getContentFormatter(org.eclipse.jface.text.source.ISourceViewer) + */ + public IContentFormatter getContentFormatter(ISourceViewer sourceViewer) { + + ContentFormatter3 formatter = new ContentFormatter3(); + formatter.setDocumentPartitioning(getConfiguredDocumentPartitioning(sourceViewer)); + + IFormattingStrategy indentationStrategy = new XmlDocumentFormattingStrategy(sourceViewer); + IFormattingStrategy elementFormattingStrategy = new XmlElementFormattingStrategy(sourceViewer); + + formatter.setFormattingStrategy(indentationStrategy); + formatter.setFormattingStrategy(indentationStrategy,IDocument.DEFAULT_CONTENT_TYPE); + formatter.setFormattingStrategy(elementFormattingStrategy,AntEditorPartitionScanner.XML_TAG); + + return formatter; + } } Index: Ant Editor/org/eclipse/ant/internal/ui/editor/format/FormattingPreferences.java =================================================================== RCS file: Ant Editor/org/eclipse/ant/internal/ui/editor/format/FormattingPreferences.java diff -N Ant Editor/org/eclipse/ant/internal/ui/editor/format/FormattingPreferences.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ Ant Editor/org/eclipse/ant/internal/ui/editor/format/FormattingPreferences.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,74 @@ +/* + * Created on Jan 29, 2004 + * + * To change the template for this generated file go to Window - Preferences - + * Java - Code Generation - Code and Comments + */ +package org.eclipse.ant.internal.ui.editor.format; + +/** + * + */ +public class FormattingPreferences { + private String canonicalIndent; + + private boolean useTabs; + + private int tabWitdth; + + private int printMargin; + + private boolean useElementWrapping; + + /** + * @return + */ + public String getCanonicalIndent() { + if (this.canonicalIndent == null) { + if (this.useTabs) { + this.canonicalIndent = "\t"; + } else { + String tab = ""; + for (int i = 0; i < this.tabWitdth; i++) { + tab = tab.concat(" "); + } + this.canonicalIndent = tab; + } + } + return this.canonicalIndent; + } + + // TODO connect to ant preferences and remove this method (?) + public void useHorizontalTabs() { + this.useTabs = true; + } + + // TODO connect to ant preferences and remove this method (?) + public void useSpacesForTab(int tabWidth) { + this.useTabs = false; + this.tabWitdth = tabWidth; + } + + // TODO connect to ant preferences and remove this method (?) + public void setPrintMargin(int column) { + this.printMargin = column; + } + /** + * @return Returns the printMargin. + */ + public int getPrintMargin() { + return this.printMargin; + } + + /** + * @return + */ + public boolean useElementWrapping() { + return this.useElementWrapping; + } + + // TODO connect to ant preferences and remove this method (?) + public void setUseElementWrapping(boolean useElementWrapping) { + this.useElementWrapping = useElementWrapping; + } +} Index: Ant Editor/org/eclipse/ant/internal/ui/editor/format/NonParsingXMLFormatter.java =================================================================== RCS file: Ant Editor/org/eclipse/ant/internal/ui/editor/format/NonParsingXMLFormatter.java diff -N Ant Editor/org/eclipse/ant/internal/ui/editor/format/NonParsingXMLFormatter.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ Ant Editor/org/eclipse/ant/internal/ui/editor/format/NonParsingXMLFormatter.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,431 @@ +/* + * Created on Jan 28, 2004 + * + * To change the template for this generated file go to Window - Preferences - + * Java - Code Generation - Code and Comments + */ +package org.eclipse.ant.internal.ui.editor.format; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; + +import org.eclipse.jface.text.Assert; + +/** + * @author shacj + * + * To change the template for this generated type comment go to Window - + * Preferences - Java - Code Generation - Code and Comments + */ +public class NonParsingXMLFormatter { + + private static class CommentReader extends TagReader { + + private boolean complete = false; + + protected void clear() { + this.complete = false; + } + + public String getStartOfTag() { + return "")) { + complete = true; + } + } + return node.toString(); + } + } + + private static class DoctypeDeclarationReader extends TagReader { + + private boolean complete = false; + + protected void clear() { + this.complete = false; + } + + public String getStartOfTag() { + return "') { + complete = true; + } + } + return node.toString(); + } + + } + + private static class ProcessingInstructionReader extends TagReader { + + private boolean complete = false; + + protected void clear() { + this.complete = false; + } + + public String getStartOfTag() { + return "' && node.toString().endsWith("?>")) { + complete = true; + } + } + return node.toString(); + } + } + + /** + * + */ + private static abstract class TagReader { + + protected Reader reader; + + private String tagText; + + protected abstract void clear(); + + public int getPostTagDepthModifier() { + return 0; + } + + public int getPreTagDepthModifier() { + return 0; + } + + abstract public String getStartOfTag(); + + public String getTagText() { + return this.tagText; + } + + public boolean isTextNode() { + return false; + } + + protected abstract String readTag() throws IOException; + + public boolean requiresInitialIndent() { + return true; + } + + public void setReader(Reader reader) throws IOException { + this.reader = reader; + this.clear(); + this.tagText = readTag(); + } + + public boolean startsOnNewline() { + return true; + } + + } + + /** + * + */ + private static class TagReaderFactory { + + // Warning: the order of the Array is important! + private static TagReader[] tagReaders = new TagReader[]{new CommentReader(), + new DoctypeDeclarationReader(), + new ProcessingInstructionReader(), + new XmlElementReader()}; + + private static TagReader textNodeReader = new TextReader(); + + public static TagReader createTagReaderFor(Reader reader) + throws IOException { + + char[] buf = new char[10]; + reader.mark(10); + reader.read(buf, 0, 10); + reader.reset(); + + String startOfTag = String.valueOf(buf); + + for (int i = 0; i < tagReaders.length; i++) { + if (startOfTag.startsWith(tagReaders[i].getStartOfTag())) { + tagReaders[i].setReader(reader); + return tagReaders[i]; + } + } + // else + textNodeReader.setReader(reader); + return textNodeReader; + + } + + } + + private static class TextReader extends TagReader { + + private boolean complete; + + private boolean isTextNode; + + protected void clear() { + this.complete = false; + } + + public String getStartOfTag() { + return ""; + } + + public boolean isTextNode() { + return this.isTextNode; + } + + protected String readTag() throws IOException { + + StringBuffer node = new StringBuffer(); + + while (!complete) { + + reader.mark(1); + int intChar = reader.read(); + if (intChar == -1) break; + + char c = (char) intChar; + if (c == '<') { + reader.reset(); + complete = true; + } else { + node.append(c); + } + } + + // if this text node is just whitespace + // remove it, except for the newlines. + if (node.length() < 1) { + this.isTextNode = false; + + } else if (node.toString().trim().length() == 0) { + String whitespace = node.toString(); + node = new StringBuffer(); + for (int i = 0; i < whitespace.length(); i++) { + char whitespaceCharacter = whitespace.charAt(i); + if (whitespaceCharacter == '\n') + node.append(whitespaceCharacter); + } + this.isTextNode = false; + + } else { + this.isTextNode = true; + } + return node.toString(); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.ant.internal.ui.editor.format.NonParsingXMLFormatter.TagReader#requiresInitialIndent() + */ + public boolean requiresInitialIndent() { + return false; + } + + public boolean startsOnNewline() { + return false; + } + } + + private static class XmlElementReader extends TagReader { + + private boolean complete = false; + + protected void clear() { + this.complete = false; + } + + public int getPostTagDepthModifier() { + if (getTagText().endsWith("/>") || getTagText().endsWith("/ >")) { + return 0; + } else if (getTagText().startsWith("' && !insideQuote) { + complete = true; + } + } + return node.toString(); + } + } + + private int depth; + + private String documentText; + + private StringBuffer formattedXml; + + private boolean lastNodeWasText; + + private FormattingPreferences prefs; + + /** + * @param reader + * @param out + * @throws IOException + */ + private void copyNode(Reader reader, StringBuffer out) throws IOException { + + TagReader tag = TagReaderFactory.createTagReaderFor(reader); + + depth = depth + tag.getPreTagDepthModifier(); + + if (!lastNodeWasText) { + + if (tag.startsOnNewline() && !hasNewlineAlready(out)) { + out.append("\n"); + } + + if (tag.requiresInitialIndent()) { + out.append(indent()); + } + } + + out.append(tag.getTagText()); + + depth = depth + tag.getPostTagDepthModifier(); + + lastNodeWasText = tag.isTextNode(); + + } + + /** + * @return + */ + public String format() { + + Assert.isNotNull(this.documentText); + Assert.isNotNull(this.prefs); + + Reader reader = new StringReader(documentText); + formattedXml = new StringBuffer(); + + depth = 0; + lastNodeWasText = false; + try { + while (true) { + reader.mark(1); + int intChar = reader.read(); + reader.reset(); + + if (intChar != -1) { + copyNode(reader, formattedXml); + } else { + break; + } + } + reader.close(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return formattedXml.toString(); + } + + /** + * @param out + * @return + */ + private boolean hasNewlineAlready(StringBuffer out) { + return out.lastIndexOf("\n") == formattedXml.length() - 1 + || out.lastIndexOf("\r") == formattedXml.length() - 1; + } + + /** + * @return + */ + private String indent() { + StringBuffer indent = new StringBuffer(30); + for (int i = 0; i < depth; i++) { + indent.append(prefs.getCanonicalIndent()); + } + return indent.toString(); + } + + /** + * @param prefs + */ + public void setFormattingPreferences(FormattingPreferences prefs) { + this.prefs = prefs; + } + + /** + * @param documentText + */ + public void setText(String documentText) { + this.documentText = documentText; + } + +} Index: Ant Editor/org/eclipse/ant/internal/ui/editor/format/XmlDocumentFormattingStrategy.java =================================================================== RCS file: Ant Editor/org/eclipse/ant/internal/ui/editor/format/XmlDocumentFormattingStrategy.java diff -N Ant Editor/org/eclipse/ant/internal/ui/editor/format/XmlDocumentFormattingStrategy.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ Ant Editor/org/eclipse/ant/internal/ui/editor/format/XmlDocumentFormattingStrategy.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,151 @@ +/* + * Created on Jan 17, 2004 + * + * To change the template for this generated file go to Window - Preferences - + * Java - Code Generation - Code and Comments + */ +package org.eclipse.ant.internal.ui.editor.format; + +import java.util.LinkedList; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import org.eclipse.ant.internal.ui.model.AntUIPlugin; +import org.eclipse.jface.text.Assert; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.TypedPosition; +import org.eclipse.jface.text.formatter.ContextBasedFormattingStrategy; +import org.eclipse.jface.text.formatter.FormattingContext; +import org.eclipse.jface.text.formatter.FormattingContextProperties; +import org.eclipse.jface.text.formatter.IFormattingContext; +import org.eclipse.jface.text.source.ISourceViewer; +import org.xml.sax.SAXException; + +/** + * @author shacj + * + * To change the template for this generated type comment go to Window - + * Preferences - Java - Code Generation - Code and Comments + */ +public class XmlDocumentFormattingStrategy extends + ContextBasedFormattingStrategy { + + /** Indentations to use by this strategy */ + private final LinkedList fIndentations = new LinkedList(); + + /** Partitions to be formatted by this strategy */ + private final LinkedList fPartitions = new LinkedList(); + + /** The position sets to keep track of during formatting */ + private final LinkedList fPositions = new LinkedList(); + + // TODO connect with preferences + private final boolean addNewlines = true; + + // TODO connect with preferences + private final String canonicalIndentStep = "\t"; + + /** + * @param viewer + */ + public XmlDocumentFormattingStrategy(ISourceViewer viewer) { + super(viewer); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.text.formatter.IFormattingStrategy#format(java.lang.String, + * boolean, java.lang.String, int[]) + */ + public void format() { + + super.format(); + + Assert.isLegal(fPartitions.size() > 0); + Assert.isLegal(fIndentations.size() > 0); + + final TypedPosition partition = (TypedPosition) fPartitions + .removeFirst(); + final String indent = fIndentations.removeFirst().toString(); + final IDocument document = getViewer().getDocument(); + + // Since we are running short on time, we'll + // format the whole document, not just a single partition. + // We can correct this later--if we want to. + + String documentText = document.get(); + + // setup formatter with preferences and format the text. + // TODO connect with ant preferences ui + FormattingPreferences prefs = new FormattingPreferences(); + prefs.useSpacesForTab(4); + + NonParsingXMLFormatter formatter = new NonParsingXMLFormatter(); + formatter.setText(documentText); + formatter.setFormattingPreferences(prefs); + + String formattedText = formatter.format(); + if(formattedText != null && ! formattedText.equals(documentText)) { + document.set(formattedText); + } + + + } + + private boolean partitionsShareLine(IDocument document, + TypedPosition partition1, TypedPosition partition2) + throws BadLocationException { + + Assert.isNotNull(document); + Assert.isNotNull(partition1); + Assert.isNotNull(partition2); + + int lineNumber1 = document.getLineOfOffset(partition1.getOffset()); + int lineNumber2 = document.getLineOfOffset(partition2.getOffset()); + return lineNumber1 == lineNumber2; + } + + /* + * @see org.eclipse.jface.text.formatter.ContextBasedFormattingStrategy#formatterStarts(org.eclipse.jface.text.formatter.IFormattingContext) + */ + public void formatterStarts(IFormattingContext context) { + super.formatterStarts(context); + + final FormattingContext current = (FormattingContext) context; + + fIndentations.addLast(current + .getProperty(FormattingContextProperties.CONTEXT_INDENTATION)); + fPartitions.addLast(current + .getProperty(FormattingContextProperties.CONTEXT_PARTITION)); + fPositions.addLast(current + .getProperty(FormattingContextProperties.CONTEXT_POSITIONS)); + + } + + /* + * @see org.eclipse.jface.text.formatter.ContextBasedFormattingStrategy#formatterStops() + */ + public void formatterStops() { + super.formatterStops(); + + fIndentations.clear(); + fPartitions.clear(); + fPositions.clear(); + } + + private SAXParser getSAXParser() { + SAXParser parser = null; + try { + parser = SAXParserFactory.newInstance().newSAXParser(); + } catch (ParserConfigurationException e) { + AntUIPlugin.log(e); + } catch (SAXException e) { + AntUIPlugin.log(e); + } + return parser; + } +} Index: Ant Editor/org/eclipse/ant/internal/ui/editor/format/XmlElementFormattingStrategy.java =================================================================== RCS file: Ant Editor/org/eclipse/ant/internal/ui/editor/format/XmlElementFormattingStrategy.java diff -N Ant Editor/org/eclipse/ant/internal/ui/editor/format/XmlElementFormattingStrategy.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ Ant Editor/org/eclipse/ant/internal/ui/editor/format/XmlElementFormattingStrategy.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,260 @@ +/* + * Created on Jan 17, 2004 + * + * To change the template for this generated file go to Window - Preferences - + * Java - Code Generation - Code and Comments + */ +package org.eclipse.ant.internal.ui.editor.format; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import org.eclipse.jface.text.Assert; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.TypedPosition; +import org.eclipse.jface.text.formatter.ContextBasedFormattingStrategy; +import org.eclipse.jface.text.formatter.FormattingContext; +import org.eclipse.jface.text.formatter.FormattingContextProperties; +import org.eclipse.jface.text.formatter.IFormattingContext; +import org.eclipse.jface.text.source.ISourceViewer; + +/** + * @author shacj + * + * To change the template for this generated type comment go to Window - + * Preferences - Java - Code Generation - Code and Comments + */ +public class XmlElementFormattingStrategy extends + ContextBasedFormattingStrategy { + + /** Indentations to use by this strategy */ + private final LinkedList fIndentations = new LinkedList(); + + /** Partitions to be formatted by this strategy */ + private final LinkedList fPartitions = new LinkedList(); + + /** The position sets to keep track of during formatting */ + private final LinkedList fPositions = new LinkedList(); + + /** + * @param viewer + */ + public XmlElementFormattingStrategy(ISourceViewer viewer) { + super(viewer); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.text.formatter.IFormattingStrategy#format(java.lang.String, + * boolean, java.lang.String, int[]) + */ + public void format() { + + super.format(); + + Assert.isLegal(fPartitions.size() > 0); + Assert.isLegal(fIndentations.size() > 0); + + final TypedPosition partition = (TypedPosition) fPartitions + .removeFirst(); + final String lineIndent = fIndentations.removeFirst().toString(); + final IDocument document = getViewer().getDocument(); + + try { + + // TODO connect to preferences + FormattingPreferences prefs = new FormattingPreferences(); + prefs.useSpacesForTab(4); + prefs.setPrintMargin(80); + prefs.setUseElementWrapping(true); + + String formatted = formatElement(document, partition, lineIndent, + prefs); + + String partitionText = document.get(partition.getOffset(), + partition.getLength()); + + if (formatted != null && !formatted.equals(partitionText)) + document.replace(partition.getOffset(), partition + .getLength(), formatted); + + } catch (BadLocationException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + } + + /** + * @param partitionText + * @param prefs + * @return + */ + private String formatElement(IDocument document, TypedPosition partition, + String indentation, FormattingPreferences prefs) + throws BadLocationException { + + final String partitionText = document.get(partition.getOffset(), + partition.getLength()); + + StringBuffer formattedElement = null; + + // do we even need to think about wrapping? + if (prefs.useElementWrapping() && !partitionText.startsWith(" prefs.getPrintMargin()) { + + List attributes = getAttributes(partitionText); + if (attributes.size() > 1) { + formattedElement = new StringBuffer(); + String startTag = elementStart(partitionText); + formattedElement.append(startTag); + formattedElement.append(' '); + formattedElement.append(attributes.get(0)); + formattedElement.append("\n"); + + for (int i = 1; i < attributes.size(); i++) { + formattedElement.append(indentation); + for (int j = 0; j < (partitionLineOffset - indentation + .length()) + + startTag.length() + 1; j++) { + formattedElement.append(' '); + } + formattedElement.append(attributes.get(i)); + formattedElement.append("\n"); + } + formattedElement.append(indentation); + for (int j = 0; j < (partitionLineOffset - indentation + .length()) + 1; j++) { + formattedElement.append(' '); + } + if (partitionText.endsWith("/>")) { + formattedElement.append("/>"); + } else if (partitionText.endsWith(">")) { + formattedElement.append(">"); + } else { + Assert.isTrue(false, "Bad Partitioner."); + } + } + } + } + return formattedElement != null ? formattedElement.toString() : null; + } + + /** + * @param partitionText + * @return + */ + private List getAttributes(String text) { + + List attributes = new ArrayList(); + + int start = firstWhitespaceIn(text); + boolean insideQuotes = false; + + boolean haveEquals = false; + int quotes = 0; + StringBuffer attributePair = new StringBuffer(); + + for (int i = start; i < text.length(); i++) { + char c = text.charAt(i); + switch (c) { + case '"': + insideQuotes = !insideQuotes; + quotes++; + attributePair.append(c); + if (!insideQuotes && haveEquals && quotes == 2) { + // we're done with this attribute + attributes.add(attributePair.toString()); + // reset + attributePair = new StringBuffer(); + quotes = 0; + haveEquals = false; + } + break; + case '=': + attributePair.append(c); + haveEquals = true; + break; + default: + if (Character.isWhitespace(c) && !insideQuotes) { + if (!Character.isWhitespace(text.charAt(i - 1)) + && attributePair.length() != 0) { + attributePair.append(' '); + } + } else { + attributePair.append(c); + } + break; + } + } + return attributes; + } + + private String elementStart(String text) { + return text.substring(0, firstWhitespaceIn(text)); + } + + /** + * @param partitionText + * @return + */ + private int firstWhitespaceIn(String text) { + for (int i = 0; i < text.length(); i++) { + if (Character.isWhitespace(text.charAt(i))) { return i; } + } + return -1; + } + + private boolean partitionsShareLine(IDocument document, + TypedPosition partition1, TypedPosition partition2) + throws BadLocationException { + + Assert.isNotNull(document); + Assert.isNotNull(partition1); + Assert.isNotNull(partition2); + + int lineNumber1 = document.getLineOfOffset(partition1.getOffset()); + int lineNumber2 = document.getLineOfOffset(partition2.getOffset()); + return lineNumber1 == lineNumber2; + } + + /* + * @see org.eclipse.jface.text.formatter.ContextBasedFormattingStrategy#formatterStarts(org.eclipse.jface.text.formatter.IFormattingContext) + */ + public void formatterStarts(IFormattingContext context) { + super.formatterStarts(context); + + final FormattingContext current = (FormattingContext) context; + + fIndentations.addLast(current + .getProperty(FormattingContextProperties.CONTEXT_INDENTATION)); + fPartitions.addLast(current + .getProperty(FormattingContextProperties.CONTEXT_PARTITION)); + fPositions.addLast(current + .getProperty(FormattingContextProperties.CONTEXT_POSITIONS)); + + } + + /* + * @see org.eclipse.jface.text.formatter.ContextBasedFormattingStrategy#formatterStops() + */ + public void formatterStops() { + super.formatterStops(); + + fIndentations.clear(); + fPartitions.clear(); + fPositions.clear(); + } +} Index: Ant Editor/org/eclipse/jface/text/formatter/ContentFormatter3.java =================================================================== RCS file: Ant Editor/org/eclipse/jface/text/formatter/ContentFormatter3.java diff -N Ant Editor/org/eclipse/jface/text/formatter/ContentFormatter3.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ Ant Editor/org/eclipse/jface/text/formatter/ContentFormatter3.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,1125 @@ +/******************************************************************************* + * Copyright (c) 2000, 2003 IBM Corporation 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: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.jface.text.formatter; + + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.eclipse.jface.text.Assert; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.BadPositionCategoryException; +import org.eclipse.jface.text.ChildDocumentManager; +import org.eclipse.jface.text.DefaultPositionUpdater; +import org.eclipse.jface.text.DocumentEvent; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IDocumentExtension3; +import org.eclipse.jface.text.IPositionUpdater; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITypedRegion; +import org.eclipse.jface.text.Position; +import org.eclipse.jface.text.Region; +import org.eclipse.jface.text.TextUtilities; +import org.eclipse.jface.text.TypedPosition; + + +/** + * Improved standard implementation of IContentFormatter. The formatter + * supports three operation modi: partition aware, partition unaware and + * context based. + *

+ * In the partition aware mode, the formatter determines the partitioning of + * the document region to be formatted. For each partition it determines all + * document positions which are affected when text changes are applied to the + * partition. Those which overlap with the partition are remembered as + * character positions. These character positions are passed over to the + * formatting strategy registered for the partition's content type. The + * formatting strategy returns a string containing the formatted document + * partition as well as the adapted character positions. The formatted + * partition replaces the old content of the partition. The remembered document + * postions are updated with the adapted character positions. In addition, all + * other document positions are accordingly adapted to the formatting changes. + *

+ * In the partition unaware mode, the document's partitioning is ignored and + * the document is considered consisting of only one partition of the content + * type IDocument.DEFAULT_CONTENT_TYPE. The formatting process + * is similar to the partition aware mode, with the exception of having only + * one partition. + *

+ * The context based mode is supported by the extension interface IContentFormatterExtension2 + * and supersedes the previous modes. Clients using context based formatting + * call the method format(IDocument, IFormattingContext) with a + * properly initialized formatting context.
The formatting context must be + * set up according to the desired formatting mode: + *

+ * Depending on the registered formatting strategies, more context information + * must be passed in the formatting context, like e.g. CONTEXT_PREFERENCES. + *

+ * Note that in context based mode the content formatter is fully reentrant, + * but not thread-safe. Formatting strategies are therefore allowed to + * recursively call the method format(IDocument, IFormattingContext). + * The formatting context is saved between calls to this method. + *

+ * Usually, clients instantiate this class and configure it before using it. + * + * @see IContentFormatter + * @see IContentFormatterExtension2 + * @see IDocument + * @see ITypedRegion + * @see Position + */ +public class ContentFormatter3 implements IContentFormatter, IContentFormatterExtension, IContentFormatterExtension2 { + + /** + * Defines a reference to either the offset or the end offset of + * a particular position. + */ + static class PositionReference implements Comparable { + + /** The referenced position */ + protected Position fPosition; + /** The reference to either the offset or the end offset */ + protected boolean fRefersToOffset; + /** The original category of the referenced position */ + protected String fCategory; + + /** + * Creates a new position reference. + * + * @param position the position to be referenced + * @param refersToOffset true if position offset should be referenced + * @param category the categpry the given position belongs to + */ + protected PositionReference(Position position, boolean refersToOffset, String category) { + fPosition= position; + fRefersToOffset= refersToOffset; + fCategory= category; + } + + /** + * Returns the offset of the referenced position. + * + * @return the offset of the referenced position + */ + protected int getOffset() { + return fPosition.getOffset(); + } + + /** + * Manipulates the offset of the referenced position. + * + * @param offset the new offset of the referenced position + */ + protected void setOffset(int offset) { + fPosition.setOffset(offset); + } + + /** + * Returns the length of the referenced position. + * + * @return the length of the referenced position + */ + protected int getLength() { + return fPosition.getLength(); + } + + /** + * Manipulates the length of the referenced position. + * + * @param the new length of the referenced position + */ + protected void setLength(int length) { + fPosition.setLength(length); + } + + /** + * Returns whether this reference points to the offset or endoffset + * of the references position. + * + * @return true if the offset of the position is referenced, false otherwise + */ + protected boolean refersToOffset() { + return fRefersToOffset; + } + + /** + * Returns the category of the referenced position. + * + * @return the category of the referenced position + */ + protected String getCategory() { + return fCategory; + } + + /** + * Returns the referenced position. + * + * @return the referenced position + */ + protected Position getPosition() { + return fPosition; + } + + /** + * Returns the referenced character position + * + * @return the referenced character position + */ + protected int getCharacterPosition() { + if (fRefersToOffset) + return getOffset(); + return getOffset() + getLength(); + } + + /* + * @see Comparable#compareTo(Object) + */ + public int compareTo(Object obj) { + + if (obj instanceof PositionReference) { + PositionReference r= (PositionReference) obj; + return getCharacterPosition() - r.getCharacterPosition(); + } + + throw new ClassCastException(); + } + } + + /** + * The position updater used to update the remembered partitions. + * + * @see IPositionUpdater + * @see DefaultPositionUpdater + */ + class NonDeletingPositionUpdater extends DefaultPositionUpdater { + + /** + * Creates a new updater for the given category. + * + * @param category the category + */ + protected NonDeletingPositionUpdater(String category) { + super(category); + } + + /* + * @see DefaultPositionUpdater#notDeleted() + */ + protected boolean notDeleted() { + return true; + } + } + + /** + * The position updater which runs as first updater on the document's positions. + * Used to remove all affected positions from their categories to avoid them + * from being regularily updated. + * + * @see IPositionUpdater + */ + class RemoveAffectedPositions implements IPositionUpdater { + /** + * @see IPositionUpdater#update(DocumentEvent) + */ + public void update(DocumentEvent event) { + removeAffectedPositions(event.getDocument()); + } + } + + /** + * The position updater which runs as last updater on the document's positions. + * Used to update all affected positions and adding them back to their + * original categories. + * + * @see IPositionUpdater + */ + class UpdateAffectedPositions implements IPositionUpdater { + + /** The affected positions */ + private int[] fPositions; + /** The offset */ + private int fOffset; + + /** + * Creates a new updater. + * + * @param positions the affected positions + * @param offset the offset + */ + public UpdateAffectedPositions(int[] positions, int offset) { + fPositions= positions; + fOffset= offset; + } + + /* + * @see IPositionUpdater#update(DocumentEvent) + */ + public void update(DocumentEvent event) { + updateAffectedPositions(event.getDocument(), fPositions, fOffset); + } + } + + + /** Internal position category used for the formatter partitioning */ + private final static String PARTITIONING= "__formatter_partitioning"; //$NON-NLS-1$ + + /** The map of slave IFormattingStrategy objects */ + private Map fStrategies; + /** + * The master IFormattingStrategy object + * @since 3.0 + */ + private IFormattingStrategy fMasterStrategy; + /** The indicator of whether the formatter operates in partition aware mode or not */ + private boolean fIsPartitionAware= true; + + /** The partition information managing document position categories */ + private String[] fPartitionManagingCategories; + /** The list of references to offset and end offset of all overlapping positions */ + private List fOverlappingPositionReferences; + /** + * The document partitioning used by this formatter. + * @since 3.0 + */ + private String fPartitioning; + /** + * The document this formatter works on. + * @since 3.0 + */ + private IDocument fDocument; + /** + * The external partition managing categories. + * @since 3.0 + */ + private String[] fExternalPartitonManagingCategories; + /** + * Indicates whether fPartitionManagingCategories must be computed. + * @since 3.0 + */ + private boolean fNeedsComputation= true; + /** + * Formatting context to use while formatting + * @since 3.0 + */ + private IFormattingContext fFormattingContext= null; + /** + * Queue of position arrays used during formatting. + * @since 3.0 + */ + private final LinkedList fPositions= new LinkedList(); + + /** + * Creates a new content formatter. + *

+ * The content formatter operates by default in the partition-aware mode. + * There are no preconfigured formatting strategies. It will use the + * default document partitioning if not further configured. The context + * based mode is enabled by calls to format(IDocument, IFormattingContext. + */ + public ContentFormatter3() { + fPartitioning= IDocumentExtension3.DEFAULT_PARTITIONING; + } + + /** + * Informs this content formatter about the names of those position categories + * which are used to manage the document's partitioning information and thus should + * be ignored when this formatter updates positions. + * + * @param categories the categories to be ignored + * @deprecated incompatible with an open set of document partitionings. The provided information is only used + * if this formatter can not compute the partition managing position categories. + */ + public void setPartitionManagingPositionCategories(String[] categories) { + fExternalPartitonManagingCategories= categories; + } + + /** + * Sets the document partitioning to be used by this formatter. + * + * @param partitioning the document partitioning + * @since 3.0 + */ + public void setDocumentPartitioning(String partitioning) { + fPartitioning= partitioning; + } + + /* + * @see org.eclipse.jface.text.formatter.IContentFormatterExtension#getDocumentPartitioning() + * + * @since 3.0 + */ + public String getDocumentPartitioning() { + return fPartitioning; + } + + /** + * Sets whether the formatter operates in partition aware mode. + * + * @param enable + * true iff partition aware mode should be + * enabled, false otherwise. + */ + public void enablePartitionAwareFormatting(boolean enable) { + fIsPartitionAware= enable; + } + + /* + * @see IContentFormatter#getFormattingStrategy(String) + */ + public IFormattingStrategy getFormattingStrategy(String contentType) { + + Assert.isNotNull(contentType); + + if (fStrategies == null) + return null; + + return (IFormattingStrategy) fStrategies.get(contentType); + } + + /* + * @see IContentFormatter#format(IDocument, IRegion) + */ + public void format(IDocument document, IRegion region) { + + fNeedsComputation= true; + fFormattingContext= null; + + final IDocument last= fDocument; + fDocument= document; + + final boolean aware= fIsPartitionAware; + try { + + final int offset= region.getOffset(); + final int length= region.getLength(); + + if (fIsPartitionAware) + formatPartitions(offset, length); + else + formatRegion(offset, length, IDocument.DEFAULT_CONTENT_TYPE); + + } finally { + + fNeedsComputation= true; + fFormattingContext= null; + fDocument= last; + fIsPartitionAware= aware; + } + } + + /* + * @see org.eclipse.jface.text.formatter.IContentFormatterExtension2#format(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.formatter.IFormattingContext) + */ + public void format(IDocument document, IFormattingContext context) { + + fNeedsComputation= true; + + final IDocument last= fDocument; + fDocument= document; + + final LinkedList previous= new LinkedList(fPositions); + fPositions.clear(); + + final IFormattingContext predecessor= fFormattingContext; + fFormattingContext= context; + + final boolean aware= fIsPartitionAware; + try { + + final Boolean all= (Boolean)context.getProperty(FormattingContextProperties.CONTEXT_DOCUMENT); + if (all == null || !all.booleanValue()) { + + final TypedPosition partition= (TypedPosition)context.getProperty(FormattingContextProperties.CONTEXT_PARTITION); + final IRegion region= (IRegion)context.getProperty(FormattingContextProperties.CONTEXT_REGION); + + if (partition != null) { + formatRegion(partition.getOffset(), partition.getLength(), partition.getType()); + } else if (region != null) { + + int offset= region.getOffset(); + int length= region.getLength(); + + final ITypedRegion[] regions= TextUtilities.computePartitioning(fDocument, fPartitioning, offset, length); + final ITypedRegion start= TextUtilities.getPartition(fDocument, fPartitioning, regions[0].getOffset()); + + final String type= start.getType(); + if (regions.length > 1) { + + if (!type.equals(IDocument.DEFAULT_CONTENT_TYPE)) { + + final int delta= offset - start.getOffset(); + offset -= delta; + length += delta; + } + + final int rest= fDocument.getLength() - length; + try { + formatMaster(offset, length); + } finally { + formatPartitions(offset, fDocument.getLength() - rest); + } + } else if (regions.length == 1) + formatRegion(offset, length, type); + } + } else { + + try { + formatMaster(0, fDocument.getLength()); + } finally { + formatPartitions(0, fDocument.getLength()); + } + } + } catch (BadLocationException exception) { + // Should not happen + + } finally { + + fNeedsComputation= true; + fFormattingContext= predecessor; + fDocument= last; + fIsPartitionAware= aware; + + fPositions.clear(); + fPositions.addAll(previous); + } + } + + /** + * Registers a slave strategy for a particular content type. + *

+ * If there is already a slave strategy registered for this type, the new + * strategy is registered instead of the old one. The content type type + * must be a valid content type of the registered partitioning of the + * formatter. + *

+ * Note that slave strategies can only be registered if a master strategy + * has been registered before. + *

+ * + * @param strategy + * The formatting strategy to register as a slave strategy, or + * null to remove an existing one + * @param type + * The content type under which to register the slave strategy + */ + public void setFormattingStrategy(IFormattingStrategy strategy, String type) { + + Assert.isNotNull(type); + + if (fStrategies == null) + fStrategies= new HashMap(); + + if (strategy == null) + fStrategies.remove(type); + else + fStrategies.put(type, strategy); + } + + /** + * Registers the master strategy for this content formatter. If there is + * already a master strategy registered, the new strategy is registered + * instead of the old one. + *

+ * Note that slave strategies can only be registered if a master strategy + * has been registered before. + *

+ * + * @param strategy + * The formatting strategy to register as the master strategy, or + * null to remove the existing one + */ + public void setFormattingStrategy(IFormattingStrategy strategy) { + fMasterStrategy= strategy; + } + + /** + * Aligns the region to a block selection. + * + * @param offset + * Offset of the region + * @param length + * Length of the region + * @return The aligned region + */ + private IRegion alignBlockSelect(int offset, int length) { + + try { + + final int aligned= fDocument.getLineOffset(fDocument.getLineOfOffset(offset)); + return new Region(aligned, length + offset - aligned); + + } catch (BadLocationException exception) { + // Should not happen + + return new Region(offset, length); + } + } + + /** + * Determines the partitioning of the given region of the document and + * formats each partition in the partitioning separately. + *

+ * The formatting strategies of each partition about the start, the + * process, and the termination of the formatting session. + * + * @param offset + * The offset of the region to be formatted + * @param length + * The length of the region to be formatted + */ + private void formatPartitions(int offset, int length) { + + try { + + final TypedPosition[] ranges= getPartitioning(offset, length); + + if (ranges != null) { + // Instead of sending indentation information for the larger + // region, allow indents to be computed with every partition. + start(ranges); + format(ranges); + stop(ranges); + } + + } catch (BadLocationException exception) { + // Can not happen + } + } + + /** + * Formats the given region with the formatting + * strategy registered for the indicated type. The + * indicated type does not necessarily have to be + * the type of the region in the documents partitioning. + *

+ * The formatting strategy is informed about the start, the process, and + * the termination of the formatting session. + * + * @param offset The offset of the region + * @param length The length of the region + * @param type The type of the region + */ + private void formatRegion(int offset, int length, String type) { + + IRegion range= null; + if (type.equals(IDocument.DEFAULT_CONTENT_TYPE)) + range= alignBlockSelect(offset, length); + else + range= new Region(offset, length); + + final IFormattingStrategy strategy= getFormattingStrategy(type); + if (strategy != null) { + + final TypedPosition region= new TypedPosition(range.getOffset(), range.getLength(), type); + + formatterStarts(strategy, region, getIndentation(region.getOffset())); + format(strategy, region); + strategy.formatterStops(); + } + } + + /** + * Formats the given region with the master formatting strategy. + *

+ * The formatting strategy is informed about the start, the process, and + * the termination of the formatting session. + * + * @param offset + * The offset of the region + * @param length + * The length of the region + * @param type + * The type of the region + */ + private void formatMaster(int offset, int length) { + + if (fMasterStrategy != null) { + + final IRegion aligned= alignBlockSelect(offset, length); + final TypedPosition region= new TypedPosition(aligned.getOffset(), aligned.getLength(), IDocument.DEFAULT_CONTENT_TYPE); + + formatterStarts(fMasterStrategy, region, getIndentation(region.getOffset())); + format(fMasterStrategy, region); + fMasterStrategy.formatterStops(); + } + } + + /** + * Fires the formatterStarts event for the indicated + * formatting strategy. + * + * @param strategy + * Formatting strategy to fire the event for + * @param region + * Region where the strategy is supposed to format + * @param indentation + * Indentation to use while formatting the region + */ + private void formatterStarts(IFormattingStrategy strategy, TypedPosition region, String indentation) { + + if (fFormattingContext != null && strategy instanceof IFormattingStrategyExtension) { + + final IFormattingStrategyExtension extension= (IFormattingStrategyExtension)strategy; + final int[] positions= getAffectedPositions(region.getOffset(), region.getLength()); + + fPositions.addLast(positions); + + fFormattingContext.setProperty(FormattingContextProperties.CONTEXT_INDENTATION, indentation); + fFormattingContext.setProperty(FormattingContextProperties.CONTEXT_PARTITION, region); + fFormattingContext.setProperty(FormattingContextProperties.CONTEXT_POSITIONS, positions); + + extension.formatterStarts(fFormattingContext); + } else + strategy.formatterStarts(indentation); + } + + /** + * Returns the partitioning of the given region of the specified document. + * As one partition after the other will be formatted and formatting will + * probably change the length of the formatted partition, it must be kept + * track of the modifications in order to submit the correct partition to all + * formatting strategies. For this, all partitions are remembered as positions + * in a dedicated position category. + * + * @param offset Offset of the region for which the partitioning must be determined + * @param length Length of the region for which the partitioning must be determined + * @return the partitioning of the specified region + * @exception BadLocationException of region is invalid in the document + */ + private TypedPosition[] getPartitioning(int offset, int length) throws BadLocationException { + + ITypedRegion[] regions= TextUtilities.computePartitioning(fDocument, fPartitioning, offset, length); + TypedPosition[] positions= new TypedPosition[regions.length]; + + for (int i= 0; i < regions.length; i++) + positions[i]= new TypedPosition(regions[i]); + + return positions; + } + + /** + * Fires the formatterStarts event to all formatting + * strategies which will be involved in the forthcoming formatting process. + * + * @param partitions + * The partitioning of the document to be formatted + * @param indentation + * The initial indentation + */ + private void start(TypedPosition[] partitions) { + + String type= null; + TypedPosition region= null; + String indentation = null; + + for (int i= partitions.length - 1; i >= 0; i--) { + + region= partitions[i]; + type= region.getType(); + + // Instead of sending indentation information for the larger + // region, allow indents to be computed with every partition. + + // Note that the indentation given for the partition is actually + // the indentation of the line in which the partition occurs. + // If multiple partitions exist on the same line the String returned + // includes all of the spaces and tabs prior to the first partition + // on the line. + + indentation = getIndentation(partitions[i].offset); + + if (!type.equals(IDocument.DEFAULT_CONTENT_TYPE)) { + + final IFormattingStrategy strategy= getFormattingStrategy(type); + if (strategy != null && strategy != fMasterStrategy) + formatterStarts(strategy, region, indentation); + } + } + } + + /** + * Formats the partitions using the formatting strategy registered for each + * partition's content type. + * + * @param partitions + * The partitioning of the document to be formatted + */ + private void format(TypedPosition[] partitions) { + + String type= null; + TypedPosition region= null; + + for (int i= partitions.length - 1; i >= 0; i--) { + + region= partitions[i]; + type= region.getType(); + + if (!type.equals(IDocument.DEFAULT_CONTENT_TYPE)) { + + final IFormattingStrategy strategy= getFormattingStrategy(type); + if (strategy != null && strategy != fMasterStrategy) { + + if (fFormattingContext != null && strategy instanceof IFormattingStrategyExtension) { + + final IFormattingStrategyExtension extension= (IFormattingStrategyExtension)strategy; + extension.format(); + + } else + format(strategy, region); + } + } + } + } + + /** + * Formats the given region in the document using the indicated strategy. + * The type of the region does not have to be the same as the type for + * which the strategy was originally registered. + *

+ * The formatting process will happen in the mode set up by the formatting + * context or changes to the partition aware/unaware property. + * + * @param strategy + * The strategy to be used + * @param region + * The region to be formatted + */ + private void format(IFormattingStrategy strategy, TypedPosition region) { + + if (fFormattingContext != null && strategy instanceof IFormattingStrategyExtension) { + + final int[] positions= (int[])fFormattingContext.getProperty(FormattingContextProperties.CONTEXT_POSITIONS); + + IPositionUpdater first= new RemoveAffectedPositions(); + fDocument.insertPositionUpdater(first, 0); + IPositionUpdater last= new UpdateAffectedPositions(positions, region.getOffset()); + fDocument.addPositionUpdater(last); + + final IFormattingStrategyExtension extension= (IFormattingStrategyExtension)strategy; + extension.format(); + + fDocument.removePositionUpdater(first); + fDocument.removePositionUpdater(last); + + } else { + + try { + + final int offset= region.getOffset(); + int length= region.getLength(); + + String content= fDocument.get(offset, length); + final int[] positions= getAffectedPositions(offset, length); + String formatted= strategy.format(content, isLineStart(offset), getIndentation(offset), positions); + + if (formatted != null && !formatted.equals(content)) { + + IPositionUpdater first= new RemoveAffectedPositions(); + fDocument.insertPositionUpdater(first, 0); + IPositionUpdater last= new UpdateAffectedPositions(positions, offset); + fDocument.addPositionUpdater(last); + + fDocument.replace(offset, length, formatted); + + fDocument.removePositionUpdater(first); + fDocument.removePositionUpdater(last); + } + + } catch (BadLocationException x) { + // should not happen + } + } + } + + /** + * Fires the formatterStops event to all formatting + * strategies which were involved in the formatting process which is about + * to terminate. + * + * @param partitions + * The partitioning of the document which has been formatted + */ + private void stop(TypedPosition[] partitions) { + + String type= null; + for (int i= partitions.length - 1; i >= 0; i--) { + + type= partitions[i].getType(); + if (!type.equals(IDocument.DEFAULT_CONTENT_TYPE)) { + + final IFormattingStrategy strategy= getFormattingStrategy(type); + if (strategy != null && strategy != fMasterStrategy) + strategy.formatterStops(); + } + } + } + + /** + * Returns the partition managing position categories for the formatted document. + * + * @return the position managing position categories + * @since 3.0 + */ + private String[] getPartitionManagingCategories() { + if (fNeedsComputation) { + fNeedsComputation= false; + fPartitionManagingCategories= TextUtilities.computePartitionManagingCategories(fDocument); + if (fPartitionManagingCategories == null) + fPartitionManagingCategories= fExternalPartitonManagingCategories; + } + return fPartitionManagingCategories; + } + + /** + * Determines whether the given document position category should be ignored + * by this formatter's position updating. + * + * @param category the category to check + * @return true if the category should be ignored, false otherwise + */ + private boolean ignoreCategory(String category) { + + if (PARTITIONING.equals(category)) + return true; + + String[] categories= getPartitionManagingCategories(); + if (categories != null) { + for (int i= 0; i < categories.length; i++) { + if (categories[i].equals(category)) + return true; + } + } + + return false; + } + + /** + * Determines all embracing, overlapping, and follow up positions + * for the given region of the document. + * + * @param offset the offset of the document region to be formatted + * @param length the length of the document to be formatted + */ + private void determinePositionsToUpdate(int offset, int length) { + + String[] categories= fDocument.getPositionCategories(); + if (categories != null) { + for (int i= 0; i < categories.length; i++) { + + if (ignoreCategory(categories[i])) + continue; + + try { + + Position[] positions= fDocument.getPositions(categories[i]); + + for (int j= 0; j < positions.length; j++) { + + Position p= positions[j]; + if (p.overlapsWith(offset, length)) { + + if (offset < p.getOffset()) + fOverlappingPositionReferences.add(new PositionReference(p, true, categories[i])); + + if (p.getOffset() + p.getLength() < offset + length) + fOverlappingPositionReferences.add(new PositionReference(p, false, categories[i])); + } + } + + } catch (BadPositionCategoryException x) { + // can not happen + } + } + } + } + + /** + * Returns all offset and the end offset of all positions overlapping with the + * specified document range. + * + * @param offset the offset of the document region to be formatted + * @param length the length of the document to be formatted + * @return all character positions of the interleaving positions + */ + private int[] getAffectedPositions(int offset, int length) { + + fOverlappingPositionReferences= new ArrayList(); + + determinePositionsToUpdate(offset, length); + + // since sort is stable, no reference pairs to the same zero-length position + // will get swapped. + Collections.sort(fOverlappingPositionReferences); + + int[] positions= new int[fOverlappingPositionReferences.size()]; + for (int i= 0; i < positions.length; i++) { + PositionReference r= (PositionReference) fOverlappingPositionReferences.get(i); + positions[i]= r.getCharacterPosition() - offset; + } + + return positions; + } + + /** + * Removes the affected positions from their categories to avoid + * that they are invalidly updated. + * + * @param document the document + */ + private void removeAffectedPositions(IDocument document) { + if (fOverlappingPositionReferences != null) { + int size= fOverlappingPositionReferences.size(); + for (int i= 0; i < size; i++) { + PositionReference r= (PositionReference)fOverlappingPositionReferences.get(i); + try { + document.removePosition(r.getCategory(), r.getPosition()); + } catch (BadPositionCategoryException x) { + // can not happen + } + } + } + } + + /** + * Updates all the overlapping positions. Note, all other positions are + * automatically updated by their document position updaters. + * + * @param document the document to has been formatted + * @param positions the adapted character positions to be used to update the document positions + * @param offset the offset of the document region that has been formatted + */ + protected void updateAffectedPositions(IDocument document, int[] positions, int offset) { + + if (document != fDocument) + return; + + if (fOverlappingPositionReferences == null || fOverlappingPositionReferences.size() == 0 || positions.length == 0) + return; + + for (int i= 0; i < positions.length; i++) { + + PositionReference r= (PositionReference) fOverlappingPositionReferences.get(i); + + if (r.refersToOffset()) { + int posOffset= offset + positions[i]; + if (posOffset >= 0) + r.setOffset(posOffset); +// else +// Protest + } else { + // positions are ordered by offset. For every position that has references + // to both offset and length, the offset comes first. + // Therefore, the end of the position (offset + positions[i]) is supposedly + // greater than r.getOffset() + // if this is not the case, perhaps the position returned from the formatter was negative? + // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=46617 + int length= offset + positions[i] - r.getOffset(); + if (length >= 0) + r.setLength(length); +// else +// Protest + } + + Position p= r.getPosition(); + String category= r.getCategory(); + if (!document.containsPosition(category, p.offset, p.length)) { + try { + if (positionAboutToBeAdded(document, category, p)) + document.addPosition(r.getCategory(), p); + } catch (BadPositionCategoryException x) { + // can not happen + } catch (BadLocationException x) { + // should not happen + } + } + + } + + fOverlappingPositionReferences= null; + } + + /** + * The given position is about to be added to the given position category of the given document.

+ * This default implementation enacts the same rule as the TextViewer, i.e. if the position is used for + * managing slave documents it is ensured that the slave document starts at a line offset. + * + * @param document the document + * @param category the position categroy + * @param position the position that will be added + * @return true if the position can be added, false if it should be ignored + */ + protected boolean positionAboutToBeAdded(IDocument document, String category, Position position) { + if (ChildDocumentManager.CHILDDOCUMENTS.equals(category)) { + /* + * We assume child document offsets to be at the beginning + * of a line. Because the formatter might have moved the + * position to be somewhere in the middle of a line we patch it here. + */ + try { + int lineOffset= document.getLineInformationOfOffset(position.offset).getOffset(); + position.setLength(position.length + position.offset - lineOffset); + position.setOffset(lineOffset); + } catch (BadLocationException x) { + return false; + } + } + return true; + } + + /** + * Returns the indentation of the line of the given offset. + * + * @param offset the offset + * @return the indentation of the line of the offset + */ + private String getIndentation(int offset) { + + try { + int start= fDocument.getLineOfOffset(offset); + start= fDocument.getLineOffset(start); + + int end= start; + char c= fDocument.getChar(end); + while ('\t' == c || ' ' == c) + c= fDocument.getChar(++end); + + return fDocument.get(start, end - start); + } catch (BadLocationException x) { + } + + return ""; //$NON-NLS-1$ + } + + /** + * Determines whether the offset is the beginning of a line in the given document. + * + * @param offset the offset + * @return true if offset is the beginning of a line + * @exception BadLocationException if offset is invalid in document + */ + private boolean isLineStart(int offset) throws BadLocationException { + int start= fDocument.getLineOfOffset(offset); + start= fDocument.getLineOffset(start); + return (start == offset); + } +}