Update of /cvsroot/e-p-i-c/org.epic.perleditor/src/org/epic/perleditor/templates/textmanipulation In directory sc8-pr-cvs1:/tmp/cvs-serv7241/src/org/epic/perleditor/templates/textmanipulation Added Files: TextRegion.java MultiTextEdit.java NopTextEdit.java MoveTextEdit.java TextEditNodeComparator.java TextBufferFactory.java TextEdit.java SimpleTextEdit.java UndoMemento.java TextRange.java TextEditNode.java TextBuffer.java TextBufferEditor.java TextManipulationMessages.java Log Message: Templating support added. --- NEW FILE: TextRegion.java --- /* * (c) Copyright IBM Corp. 2000, 2001. * All Rights Reserved. */ package org.epic.perleditor.templates.textmanipulation; /** * A text region describes a certain range in an <code>ITextBuffer</code>. A region is defined by * its offset into the text buffer and its length. * <p> * A region is considered a value object. Its offset or length do not change over time. </p> * <p> * <bf>NOTE:<bf> This class/interface is part of an interim API that is still under development * and expected to change significantly before reaching stability. It is being made available at * this early stage to solicit feedback from pioneering adopters on the understanding that any * code that uses this API will almost certainly be broken (repeatedly) as the API evolves.</p> */ // This class avoids contamination of clients with wrong imports. public abstract class TextRegion { /** * Returns the offset of the region. * * @return the offset of the region */ public abstract int getOffset(); /** * Returns the length of the region. * * @return the length of the region */ public abstract int getLength(); } --- NEW FILE: MultiTextEdit.java --- /* * (c) Copyright IBM Corp. 2000, 2001. * All Rights Reserved. */ package org.epic.perleditor.templates.textmanipulation; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.eclipse.core.internal.utils.Assert; import org.eclipse.core.runtime.CoreException; public class MultiTextEdit { private List fChildren; /** * Creates a new composite text edit. */ public MultiTextEdit() { fChildren= new ArrayList(3); } protected MultiTextEdit(List children) throws CoreException { fChildren= new ArrayList(children.size()); for (Iterator iter= children.iterator(); iter.hasNext();) { fChildren.add(((TextEdit)iter.next()).copy()); } } protected List getChildren() { return fChildren; } /** * Adds all <code>TextEdits</code> managed by the given multt text edit. * * @param edit the multi text edit to be added. */ public void add(MultiTextEdit edit) { Assert.isNotNull(edit); fChildren.add(edit); } /** * Adds a text edit. * * @param edit the text edit to be added */ public void add(TextEdit edit) { Assert.isNotNull(edit); fChildren.add(edit); } /** * Returns the children managed by this text edit collection. * * @return the children of this composite text edit */ public Iterator iterator() { return fChildren.iterator(); } /** * Connects this text edit to the given <code>TextBufferEditor</code>. * Note that this method <b>should only be called</b> by a <code> * TextBufferEditor</code>. *<p> * This default implementation does nothing. Subclasses may override * if needed. * * @param editor the text buffer editor this text edit has been added to */ public void connect(TextBufferEditor editor) throws CoreException { for (Iterator iter= fChildren.iterator(); iter.hasNext();) { Object element= iter.next(); if (element instanceof TextEdit) editor.add((TextEdit)element); else editor.add((MultiTextEdit)element); } } /** * Creates and returns a copy of this text edit collection. The copy method should * be implemented in a way so that the copy can be added to a different <code> * TextBuffer</code> without causing any harm to the object from which the copy * has been created. * * @return a copy of this object. */ public MultiTextEdit copy() throws CoreException { return new MultiTextEdit(fChildren); } /** * Returns the <code>TextRange</code> that this text edit is going to * manipulate. If this method is called before the <code>MultiTextEdit</code> * has been added to a <code>TextBufferEditor</code> it may return <code> * null</code> to indicate this situation. * * @return the <code>TextRange</code>s this <code>TextEdit is going * to manipulate */ public TextRange getTextRange() { int size= fChildren.size(); if (size == 0) return new TextRange(0,0); TextRange range= ((TextEdit)fChildren.get(0)).getTextRange(); int start= range.getOffset(); int end= range.getInclusiveEnd(); for (int i= 1; i < size; i++) { range= ((TextEdit)fChildren.get(i)).getTextRange(); start= Math.min(start, range.getOffset()); end= Math.max(end, range.getInclusiveEnd()); } return new TextRange(start, end - start + 1); } /** * Returns the element modified by this text edit. The method * may return <code>null</code> if the modification isn't related to a * element or if the content of the modified text buffer doesn't * follow any syntax. * <p> * This default implementation returns <code>null</code> * * @return the element modified by this text edit */ public Object getModifiedElement() { return null; } } --- NEW FILE: NopTextEdit.java --- /* * (c) Copyright IBM Corp. 2000, 2001. * All Rights Reserved. */ package org.epic.perleditor.templates.textmanipulation; import org.eclipse.core.runtime.CoreException; /** * A text edit that does nothing. A <code>NopTextEdit</code> can be used to track * positions when executing <code>TextEdits</code> associated with a <code> * TextBufferEditor</code>. */ public class NopTextEdit extends TextEdit { private TextRange fTextRange; /** * Creates a new <code>NopTextEdit</code> for the given * offset and length. * * @param offset the starting offset this text edit is "working on" * @param length the length this text edit is "working on" */ public NopTextEdit(int offset, int length) { this(new TextRange(offset, length)); } /** * Creates a new <code>NopTextEdit</code> for the given * range. * * @param range the <code>TextRange</code> this text edit is "working on" */ public NopTextEdit(TextRange range) { fTextRange= range; } /* non Java-doc * @see TextEdit#getTextRange */ public TextRange getTextRange() { return fTextRange; } /* non Java-doc * @see TextEdit#perform */ public TextEdit perform(TextBuffer buffer) throws CoreException { return new NopTextEdit(fTextRange); } /* non Java-doc * @see TextEdit#perform */ public TextEdit copy() { return new NopTextEdit(fTextRange.copy()); } } --- NEW FILE: MoveTextEdit.java --- /* * (c) Copyright IBM Corp. 2000, 2001. * All Rights Reserved. */ package org.epic.perleditor.templates.textmanipulation; import org.eclipse.core.runtime.CoreException; //import org.eclipse.jdt.internal.corext.Assert; /** * A text edit that moves text inside a text buffer. */ public final class MoveTextEdit extends TextEdit { /* package */ static class TargetMark extends NopTextEdit { private MoveTextEdit fMoveTextEdit; public TargetMark(TextRange range, MoveTextEdit edit) { super(range); fMoveTextEdit= edit; } /* package */ MoveTextEdit getMoveTextEdit() { return fMoveTextEdit; } public TextEdit perform(TextBuffer buffer) throws CoreException { fMoveTextEdit.internalPerform(buffer); return super.perform(buffer); } public TextEdit copy() { // Assert.isTrue(false, "This should never happen"); //$NON-NLS-1$ return super.copy(); } } private TextRange fTarget; private TextRange fSource; private int fPerformCounter; /** * Creates a new <code>MoveTextEdit</code>. The text edit doesn't support * overlapping moves. So for a <code>MoveTextEdit</code> <code>destination <= offset && * offset + length - 1 <= destination</code> must be <code>true</code>. * * @param offset the offset of the text to be moved * @param length the text length to be moved * @param destination the destination offset */ public MoveTextEdit(int offset, int length, int destination) { // Assert.isTrue(destination <= offset || offset + length <= destination); fSource= new TextRange(offset, length); fTarget= new TextRange(destination); } /** * Creates a new <code>MoveTextEdit</code> with the given source and target range. * * @param source the source * @param target the target */ private MoveTextEdit(TextRange source,TextRange target) { fSource= source; fTarget= target; } /** * Returns the move text edit's source range. This method returns the same range * as <code>TextEdit#getTextRange()</code> * * @return the edit's source range */ public TextRange getSourceRange() { return fSource; } /** * Returns the move text edit's target range. * * @return the edit's target range */ public TextRange getTargetRange() { return fTarget; } /* non Java-doc * @see TextEdit#getTextRange() */ public TextRange getTextRange() { return fSource; } /* non Java-doc * @see TextEdit#connect(TextBufferEditor) */ public void connect(TextBufferEditor editor) throws CoreException { editor.add(new TargetMark(fTarget, this)); } /* non Java-doc * @see TextEdit#perform(TextBuffer) */ public TextEdit perform(TextBuffer buffer) throws CoreException { internalPerform(buffer); return new MoveTextEdit(fTarget, fSource); } /* non Java-doc * @see TextEdit#copy() */ public TextEdit copy() { TextRange source= getSourceRange(); TextRange target= getTargetRange(); return new MoveTextEdit(source.fOffset, source.fLength, target.fOffset); } //---- Helper method --------------------------------------------------------------------------------- private void internalPerform(TextBuffer buffer) throws CoreException { // Assert.isTrue(fPerformCounter < 2); if (++fPerformCounter == 2) { TextRange source= getSourceRange(); TextRange target= getTargetRange(); String current= buffer.getContent(source.fOffset, source.fLength); buffer.replace(source, ""); //$NON-NLS-1$ buffer.replace(target, current); } } /* package */ boolean isUpMove() { return fSource.fOffset < fTarget.fOffset; } /* package */ boolean isDownMove() { return fSource.fOffset > fTarget.fOffset; } /* package */ TextRange getChildRange() { int offset= fSource.fOffset; int length= fSource.fLength; int destination= fTarget.fOffset; if (destination <= offset) return new TextRange(destination, offset + length - destination); else return new TextRange(offset, destination - offset); } } --- NEW FILE: TextEditNodeComparator.java --- /* * (c) Copyright IBM Corp. 2000, 2001. * All Rights Reserved. */ package org.epic.perleditor.templates.textmanipulation; import java.util.Comparator; /** * A special comparator to comapre <code>TextRange</code>s. */ /* package */ class TextEditNodeComparator implements Comparator { public int compare(Object o1, Object o2) { TextEditNode node1= (TextEditNode)o1; TextEditNode node2= (TextEditNode)o2; TextRange pos1= node1.getTextRange(); TextRange pos2= node2.getTextRange(); int offset1= pos1.fOffset; int offset2= pos2.fOffset; if (offset1 < offset2) return -1; if (offset1 > offset2) return 1; // same offset int length1= pos1.fLength; int length2= pos2.fLength; // insertion points come before anything else at the same position. if (length1 == 0 && length2 != 0) return -1; if (length1 != 0 && length2 == 0) return 1; // Longer edits come before shorter edits if (length1 < length2) return 1; if (length1 > length2) return -1; if (node1.fEdit.index < node2.fEdit.index) return -1; return 1; } } --- NEW FILE: TextBufferFactory.java --- /* * (c) Copyright IBM Corp. 2000, 2001. * All Rights Reserved. */ package org.epic.perleditor.templates.textmanipulation; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.HashMap; import java.util.Map; //import net.sourceforge.phpdt.internal.corext.util.IOCloser; import org.epic.perleditor.templates.util.IOCloser; //import net.sourceforge.phpdt.internal.ui.PHPStatusConstants; import org.epic.perleditor.templates.ui.EPICStatusConstants; //import net.sourceforge.phpeclipse.PHPeclipsePlugin; import org.epic.perleditor.PerlEditorPlugin; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IWorkspaceRunnable; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.text.Document; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.source.IAnnotationModel; import org.eclipse.jface.util.Assert; import org.eclipse.ui.part.FileEditorInput; import org.eclipse.ui.texteditor.IDocumentProvider; /* package */ class TextBufferFactory { private IDocumentProvider fDocumentProvider; private Map fFileValueMap; private Map fBufferValueMap; private static class Value { TextBuffer buffer; FileEditorInput input; IDocument document; IAnnotationModel annotationModel; int references; public Value(TextBuffer b, FileEditorInput i, IDocument d, IAnnotationModel m) { buffer= b; input= i; document= d; annotationModel= m; } } public TextBufferFactory() { // XXX http://dev.eclipse.org/bugs/show_bug.cgi?id=5170 // Need way to map a file to a document without knowing any kind of document provider. this(PerlEditorPlugin.getDefault().getDocumentProvider()); } public TextBufferFactory(IDocumentProvider provider) { fDocumentProvider= provider; Assert.isNotNull(fDocumentProvider); fFileValueMap= new HashMap(5); fBufferValueMap= new HashMap(5); } public TextBuffer acquire(IFile file) throws CoreException { FileEditorInput input= new FileEditorInput(file); Value value= (Value)fFileValueMap.get(input); if (value != null) { value.references++; return value.buffer; } fDocumentProvider.connect(input); IDocument document= fDocumentProvider.getDocument(input); IAnnotationModel annotationModel= fDocumentProvider.getAnnotationModel(input); annotationModel.connect(document); value= new Value(new TextBuffer(document), input, document, annotationModel); fFileValueMap.put(input, value); fBufferValueMap.put(value.buffer, value); value.references++; return value.buffer; } public void release(TextBuffer buffer) { final Value value= (Value)fBufferValueMap.get(buffer); if (value == null) return; value.references--; if (value.references == 0) { buffer.release(); value.annotationModel.disconnect(value.document); fDocumentProvider.disconnect(value.input); fFileValueMap.remove(value.input); fBufferValueMap.remove(buffer); } } public void commitChanges(TextBuffer buffer, boolean force, IProgressMonitor pm) throws CoreException { final Value value= (Value)fBufferValueMap.get(buffer); if (value == null) return; boolean save= force || fDocumentProvider.mustSaveDocument(value.input); if (save) { IWorkspaceRunnable action= new IWorkspaceRunnable() { public void run(IProgressMonitor pm) throws CoreException { fDocumentProvider.aboutToChange(value.input); fDocumentProvider.saveDocument(pm, value.input, value.document, true); } }; try { ResourcesPlugin.getWorkspace().run(action, pm); } finally { fDocumentProvider.changed(value.input); } } } public TextBuffer create(IFile file) throws CoreException { FileEditorInput input= new FileEditorInput(file); IDocument document= fDocumentProvider.getDocument(input); if (document != null) { return new TextBuffer(new Document(document.get())); } else { return createFromFile(file); } } private TextBuffer createFromFile(IFile file) throws CoreException { IDocument document; // Fix for http://dev.eclipse.org/bugs/show_bug.cgi?id=19319 InputStream stream= file.getContents(); InputStreamReader in= null; try { document= new Document(); in= new InputStreamReader(new BufferedInputStream(stream), ResourcesPlugin.getEncoding()); StringBuffer buffer= new StringBuffer(); char[] readBuffer= new char[2048]; int n= in.read(readBuffer); while (n > 0) { buffer.append(readBuffer, 0, n); n= in.read(readBuffer); } document.set(buffer.toString()); return new TextBuffer(document); } catch (IOException x) { IStatus s= new Status(IStatus.ERROR, PerlEditorPlugin.getPluginId(), EPICStatusConstants.INTERNAL_ERROR, x.getMessage(), x); throw new CoreException(s); } finally { IOCloser.perform(in, stream); } } public TextBuffer create(String content) { return new TextBuffer(new Document(content)); } public void save(TextBuffer buffer, IProgressMonitor pm) throws CoreException { Value value= (Value)fBufferValueMap.get(buffer); if (value == null) throwNotManaged(); fDocumentProvider.saveDocument(pm, value.input, value.document, true); } public void aboutToChange(TextBuffer buffer) throws CoreException { Value value= (Value)fBufferValueMap.get(buffer); if (value == null) throwNotManaged(); fDocumentProvider.aboutToChange(value.input); } public void changed(TextBuffer buffer) throws CoreException { Value value= (Value)fBufferValueMap.get(buffer); if (value == null) throwNotManaged(); fDocumentProvider.changed(value.input); } private void throwNotManaged() throws CoreException { IStatus s= new Status(IStatus.ERROR, PerlEditorPlugin.getPluginId(), EPICStatusConstants.INTERNAL_ERROR, TextManipulationMessages.getString("TextBufferFactory.bufferNotManaged"), null); //$NON-NLS-1$ throw new CoreException(s); } } --- NEW FILE: TextEdit.java --- /* * (c) Copyright IBM Corp. 2000, 2001. * All Rights Reserved. */ package org.epic.perleditor.templates.textmanipulation; import org.eclipse.core.runtime.CoreException; /** * A text edit describes an elementary text manipulation operation. Text edits * are executed by adding them to a <code>TextBufferEditor</code> and then * calling <code>perform</code> on the <code>TextBufferEditor</code>. * <p> * After a <code>TextEdit</code> has been added to a <code>TextBufferEditor</code> * the method <code>connect</code> is sent to the text edit. A <code>TextEdit</code> * is allowed to do some adjustments of the text range it is going to manipulate while inside * the hook <code>connect</code>. * * @see TextBufferEditor */ public abstract class TextEdit { // index that determines the insertion order into a text buffer /* package */ int index; /* package */ boolean isSynthetic; /** * Connects this text edit to the given <code>TextBufferEditor</code>. A text edit * must not keep a reference to the passed text buffer editor. It is guaranteed that * the buffer passed to <code>perform<code> is equal to the buffer managed by * the given text buffer editor. But they don't have to be identical. * <p> * Note that this method <b>should only be called</b> by a <code> * TextBufferEditor</code>. *<p> * This default implementation does nothing. Subclasses may override * if needed. * * @param editor the text buffer editor this text edit has been added to */ public void connect(TextBufferEditor editor) throws CoreException { // does nothing } /** * Returns the <code>TextRange</code> that this text edit is going to * manipulate. If this method is called before the <code>TextEdit</code> * has been added to a <code>TextBufferEditor</code> it may return <code> * null</code> or <code>TextRange.UNDEFINED</code> to indicate this situation. * * @return the <code>TextRange</code>s this <code>TextEdit is going * to manipulate */ public abstract TextRange getTextRange(); /** * Performs the text edit. Note that this method <b>should only be called</b> * by a <code>TextBufferEditor</code>. * * @param buffer the actual buffer to manipulate * @return a text edit that can undo this text edit */ public abstract TextEdit perform(TextBuffer buffer) throws CoreException; /** * This method gets called after all <code>TextEdit</code>s added to a text buffer * editor are executed. Implementors of this method can do some clean-up or can * release allocated resources that are now longer needed. * <p> * This default implementation does nothing. */ public void performed() { // do nothing } /** * Creates and returns a copy of this object. The copy method should * be implemented in a way so that the copy can be added to a different * <code>TextBufferEditor</code> without causing any harm to the object * from which the copy has been created. * * @return a copy of this object. */ public abstract TextEdit copy() throws CoreException; /** * Returns the element modified by this text edit. The method * may return <code>null</code> if the modification isn't related to a * element or if the content of the modified text buffer doesn't * follow any syntax. * <p> * This default implementation returns <code>null</code> * * @return the element modified by this text edit */ public Object getModifiedElement() { return null; } } --- NEW FILE: SimpleTextEdit.java --- /* * (c) Copyright IBM Corp. 2000, 2001. * All Rights Reserved. */ package org.epic.perleditor.templates.textmanipulation; import org.eclipse.core.runtime.CoreException; //import org.eclipse.jdt.internal.corext.Assert; public abstract class SimpleTextEdit extends TextEdit { private TextRange fRange; private String fText; public static SimpleTextEdit createReplace(int offset, int length, String text) { return new SimpleTextEditImpl(offset, length, text); } public static SimpleTextEdit createInsert(int offset, String text) { return new SimpleTextEditImpl(offset, 0, text); } public static SimpleTextEdit createDelete(int offset, int length) { return new SimpleTextEditImpl(offset, length, ""); //$NON-NLS-1$ } private final static class SimpleTextEditImpl extends SimpleTextEdit { protected SimpleTextEditImpl(TextRange range, String text) { super(range, text); } protected SimpleTextEditImpl(int offset, int length, String text) { super(offset, length, text); } public TextEdit copy() { return new SimpleTextEditImpl(getTextRange().copy(), getText()); } } protected SimpleTextEdit() { this(TextRange.UNDEFINED, ""); //$NON-NLS-1$ } protected SimpleTextEdit(int offset, int length, String text) { this(new TextRange(offset, length), text); } protected SimpleTextEdit(TextRange range, String text) { // Assert.isNotNull(range); // Assert.isNotNull(text); fRange= range; fText= text; } /** * Returns the text edit's text * * @return the text edit's text */ public String getText() { return fText; } /** * Sets the text edit's text * <p> * This method should only be called from within the <code> * connect</code> method. * * @param text the text edit's text */ protected final void setText(String text) { fText= text; // Assert.isNotNull(fText); } /** * Sets the text edit's range. * <p> * This method should only be called from within the <code> * connect</code> method. * * @param range the text edit's range. */ protected void setTextRange(TextRange range) { fRange= range; // Assert.isNotNull(fRange); } /* non Java-doc * @see TextEdit#getTextRange */ public TextRange getTextRange() { return fRange; } /* non Java-doc * @see TextEdit#doPerform */ public final TextEdit perform(TextBuffer buffer) throws CoreException { String current= buffer.getContent(fRange.fOffset, fRange.fLength); buffer.replace(fRange, fText); return new SimpleTextEditImpl(fRange, current); } } --- NEW FILE: UndoMemento.java --- /* * (c) Copyright IBM Corp. 2000, 2001. * All Rights Reserved. */ package org.epic.perleditor.templates.textmanipulation; import java.util.ArrayList; import java.util.List; /** * This class encapsulates the reverse change of a number of <code>TextEdit</code>s * executed on a <code>TextBufferEditor</code> */ public final class UndoMemento { /* package */ int fMode; /* package */ List fEdits; /* package */ UndoMemento(int mode) { fMode= mode; fEdits= new ArrayList(10); } /* package */ void add(TextEdit edit) { fEdits.add(edit); } } --- NEW FILE: TextRange.java --- /* * (c) Copyright IBM Corp. 2000, 2001. * All Rights Reserved. */ package org.epic.perleditor.templates.textmanipulation; //import org.eclipse.jdt.core.ISourceRange; // //import org.eclipse.jdt.internal.corext.Assert; public final class TextRange { /* package */ int fOffset; /* package */ int fLength; public static final TextRange UNDEFINED= new TextRange((TextRange)null); /** * Creates a insert position with the given offset. * * @param offset the position offset, must be >= 0 */ public TextRange(int offset) { this(offset, 0); } /** * Creates a new range with the given offset and length. * * @param offset the position offset, must be >= 0 * @param length the position length, must be >= 0 */ public TextRange(int offset, int length) { fOffset= offset; // Assert.isTrue(fOffset >= 0); fLength= length; // Assert.isTrue(fLength >= 0); } /** * Constructor for the undefined text range. */ private TextRange(TextRange dummy) { fOffset= -1; fLength= -1; } public static TextRange createFromStartAndLength(int start, int length) { return new TextRange(start, length); } public static TextRange createFromStartAndInclusiveEnd(int start, int end) { return new TextRange(start, end - start + 1); } public static TextRange createFromStartAndExclusiveEnd(int start, int end) { return new TextRange(start, end - start); } /** * Creates a new range from the given source range. * * @range the source range denoting offset and length */ // public TextRange(ISourceRange range) { // this(range.getOffset(), range.getLength()); // } /** * Returns the offset of this range. * * @return the length of this range */ public int getOffset() { return fOffset; } /** * Returns the length of this range. * * @return the length of this range */ public int getLength() { return fLength; } /** * Returns the inclusive end position of this range. That means that the end position * denotes the last character of this range. * * @return the inclusive end position */ public int getInclusiveEnd() { return fOffset + fLength - 1; } /** * Returns the exclusive end position of this range. That means that the end position * denotes the first character after this range. * * @return the exclusive end position */ public int getExclusiveEnd() { return fOffset + fLength; } /** * Creates a copy of this <code>TextRange</code>. * * @return a copy of this <code>TextRange</code> */ public TextRange copy() { if (isUndefined()) return this; return new TextRange(fOffset, fLength); } /** * Returns <code>true</code> if this text range is the <code>UNDEFINED</code> * text range. Otherwise <code>false</code> is returned. */ public boolean isUndefined() { return UNDEFINED == this; } /** * Checks if this <code>TextRange</code> is valid. For valid text range the following * expression evaluates to <code>true</code>: * <pre> * getOffset() >= 0 && getLength() >= 0 * </pre> * * @return <code>true</code> if this text range is a valid range. Otherwise <code> * false</code> */ public boolean isValid() { return fOffset >= 0 && fLength >= 0; } /* package */ boolean isInsertionPoint() { return fLength == 0; } /* package */ boolean equals(TextRange range) { return fOffset == range.fOffset && fLength == range.fLength; } /* package */ boolean isEqualInsertionPoint(TextRange range) { return fLength == 0 && range.fLength == 0 && fOffset == range.fOffset; } /* package */ boolean liesBehind(TextRange range) { return fOffset >= range.fOffset + range.fLength; } /* package */ boolean isInsertionPointAt(int o) { return fOffset == o && fLength == 0; } /* package */ boolean covers(TextRange other) { if (fLength == 0) { // an insertion point can't cover anything return false; } else if (other.fLength == 0) { int otherOffset= other.fOffset; return fOffset < otherOffset && otherOffset < fOffset + fLength; } else { int otherOffset= other.fOffset; return fOffset <= otherOffset && otherOffset + other.fLength <= fOffset + fLength; } } /* non Java-doc * @see Object#toString() */ public String toString() { StringBuffer buffer= new StringBuffer(); buffer.append(TextManipulationMessages.getString("TextRange.offset")); //$NON-NLS-1$ buffer.append(fOffset); buffer.append(TextManipulationMessages.getString("TextRange.length")); //$NON-NLS-1$ buffer.append(fLength); return buffer.toString(); } public boolean equals(Object obj) { if (! (obj instanceof TextRange)) return false; TextRange other= (TextRange)obj; return fOffset == other.getOffset() && fLength == other.getLength(); } public int hashCode() { return fOffset ^ fLength; } } --- NEW FILE: TextEditNode.java --- /* * (c) Copyright IBM Corp. 2000, 2001. * All Rights Reserved. */ package org.epic.perleditor.templates.textmanipulation; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jface.text.DocumentEvent; import org.eclipse.jface.text.IDocumentListener; /** * A helper class to arrange <code>TextEdit</code>s into a tree to optimize their * execution. */ /* package */ abstract class TextEditNode { /* package */ TextEditNode fParent; /* package */ List fChildren; /* package */ TextEdit fEdit; /* package */ static class DefaultNode extends TextEditNode { public DefaultNode(TextEdit edit) { super(edit); } } /* package */ static class RootNode extends TextEditNode { private int fUndoIndex; public RootNode(int length) { super(new NopTextEdit(new TextRange(0, length))); fEdit.isSynthetic= true; } public boolean covers(TextEditNode node) { return true; } public UndoMemento performDo(TextBuffer buffer, IProgressMonitor pm) throws CoreException { DoRangeUpdater updater= new DoRangeUpdater(); UndoMemento undo= new UndoMemento(TextBufferEditor.UNDO); try { buffer.registerUpdater(updater); performDo(buffer, updater, undo, pm); } finally { buffer.unregisterUpdater(updater); updater.setActiveNode(null); } return undo; } public UndoMemento performUndo(TextBuffer buffer, IProgressMonitor pm) throws CoreException { UndoRangeUpdater updater= new UndoRangeUpdater(this); UndoMemento undo= new UndoMemento(TextBufferEditor.REDO); try { buffer.registerUpdater(updater); performUndo(buffer, updater, undo, pm); } finally { buffer.unregisterUpdater(updater); updater.setActiveNode(null); } return undo; } protected void setUndoIndex(int index) { fUndoIndex= index; } protected int getUndoIndex() { return fUndoIndex; } } /* package */ abstract static class AbstractMoveNode extends TextEditNode { private int state; private int fTargetIndex; private int fSourceIndex; private List fAffectedChildren; public AbstractMoveNode(TextEdit edit) { super(edit); reset(); } protected abstract TextRange getSourceRange(); protected abstract TextRange getTargetRange(); protected abstract boolean isUpMove(); protected boolean isDownMove() { return !isUpMove(); } public boolean isMove() { return true; } protected void checkRange(DocumentEvent event) { TextRange range= getChildRange(); int eventOffset= event.getOffset(); int eventLength= event.getLength(); int eventEnd = eventOffset + eventLength - 1; // "Edit changes text that lies outside its defined range" // Assert.isTrue(range.fOffset <= eventOffset && eventEnd <= range.getInclusiveEnd()); } protected boolean activeNodeChanged(int delta) { TextRange targetRange= getTargetRange(); TextRange sourceRange= getSourceRange(); switch (state) { case 0: // the move delete init(); // Assert.isTrue(Math.abs(delta) == sourceRange.fLength); if (isUpMove()) { updateOffset(fAffectedChildren, delta); targetRange.fOffset+= delta; } sourceRange.fLength= 0; state= 1; break; case 1: TextEditNode target= (TextEditNode)fParent.fChildren.get(fTargetIndex); TextEditNode source= (TextEditNode)fParent.fChildren.get(fSourceIndex); updateOffset(source.fChildren, targetRange.fOffset - sourceRange.fOffset); target.fChildren= source.fChildren; if (target.fChildren != null) { for (Iterator iter= target.fChildren.iterator(); iter.hasNext();) { ((TextEditNode)iter.next()).fParent= target; } } source.fChildren= null; if (isDownMove()) { updateOffset(fAffectedChildren, delta); sourceRange.fOffset+= delta; } targetRange.fLength= delta; reset(); break; } return true; } private static void updateOffset(List nodes, int delta) { if (nodes == null) return; for (int i= nodes.size() - 1; i >= 0; i--) { TextEditNode node= (TextEditNode)nodes.get(i); TextRange range= node.getTextRange(); range.fOffset+= delta; updateOffset(node.fChildren, delta); } } private void init() { TextRange source= getSourceRange(); TextRange target= getTargetRange(); List children= fParent.fChildren; for (int i= children.size() - 1; i >= 0; i--) { TextEditNode child= (TextEditNode)children.get(i); TextRange range= child.fEdit.getTextRange(); if (range == source) fSourceIndex= i; else if (range == target) fTargetIndex= i; } int start= Math.min(fTargetIndex, fSourceIndex); int end= Math.max(fTargetIndex, fSourceIndex); fAffectedChildren= new ArrayList(3); for (int i= start + 1; i < end; i++) { fAffectedChildren.add(children.get(i)); } } private void reset() { state= 0; fSourceIndex= -1; fTargetIndex= -1; } } /* package */ static class MoveNode extends AbstractMoveNode { public MoveNode(TextEdit edit) { super(edit); } protected TextRange getChildRange() { return ((MoveTextEdit)fEdit).getChildRange(); } protected TextRange getSourceRange() { return ((MoveTextEdit)fEdit).getSourceRange(); } protected TextRange getTargetRange() { return ((MoveTextEdit)fEdit).getTargetRange(); } protected boolean isUpMove() { return ((MoveTextEdit)fEdit).isUpMove(); } public boolean isMovePartner(TextEditNode other) { if (!(other instanceof TargetMarkNode)) return false; return fEdit == ((MoveTextEdit.TargetMark)other.fEdit).getMoveTextEdit(); } public boolean covers(TextEditNode node) { if (node instanceof TargetMarkNode) { MoveTextEdit.TargetMark edit= (MoveTextEdit.TargetMark)node.fEdit; if (edit.getMoveTextEdit() == fEdit) return false; } return getParentRange().covers(node.getChildRange()); } } /* package */ static class TargetMarkNode extends AbstractMoveNode { public TargetMarkNode(TextEdit edit) { super(edit); } protected TextRange getChildRange() { return ((MoveTextEdit.TargetMark)fEdit).getMoveTextEdit().getChildRange(); } protected TextRange getSourceRange() { return ((MoveTextEdit.TargetMark)fEdit).getMoveTextEdit().getSourceRange(); } protected TextRange getTargetRange() { return ((MoveTextEdit.TargetMark)fEdit).getMoveTextEdit().getTargetRange(); } protected boolean isUpMove() { return ((MoveTextEdit.TargetMark)fEdit).getMoveTextEdit().isUpMove(); } public boolean isMovePartner(TextEditNode other) { return ((MoveTextEdit.TargetMark)fEdit).getMoveTextEdit() == other.fEdit; } } //---- Range updating --------------------------------------------------------------------------- private static abstract class RangeUpdater implements IDocumentListener { protected TextEditNode fActiveNode; public void documentAboutToBeChanged(DocumentEvent event) { } public void setActiveNode(TextEditNode node) { fActiveNode= node; } public void updateParents(int delta) { TextEditNode node= fActiveNode.fParent; while (node != null) { node.childNodeChanged(delta); node= node.fParent; } } public static int getDelta(DocumentEvent event) { return (event.getText() == null ? 0 : event.getText().length()) - event.getLength(); } } private static class DoRangeUpdater extends RangeUpdater { private List fProcessedNodes= new ArrayList(10); public void setActiveNode(TextEditNode node) { if (fActiveNode != null) fProcessedNodes.add(fActiveNode); super.setActiveNode(node); } public void documentChanged(DocumentEvent event) { fActiveNode.checkRange(event); int delta= getDelta(event); if (!fActiveNode.activeNodeChanged(delta)) { for (Iterator iter= fProcessedNodes.iterator(); iter.hasNext();) { ((TextEditNode)iter.next()).previousNodeChanged(delta); } } updateParents(delta); } } private static class UndoRangeUpdater extends RangeUpdater { private RootNode fRootNode; public UndoRangeUpdater(RootNode root) { fRootNode= root; } public void setActiveNode(TextEditNode node) { super.setActiveNode(node); } public void documentChanged(DocumentEvent event) { fActiveNode.checkRange(event); int delta= getDelta(event); if (!fActiveNode.activeNodeChanged(delta)) { int start= fRootNode.getUndoIndex() + 1; List children= fRootNode.fChildren; int size= children != null ? children.size() : 0; for (int i= start; i < size; i++) { updateUndo((TextEditNode)children.get(i), delta); } } updateParents(delta); } private void updateUndo(TextEditNode node, int delta) { node.previousNodeChanged(delta); List children= node.fChildren; int size= children != null ? children.size() : 0; for (int i= 0; i < size; i++) { updateUndo((TextEditNode)children.get(i), delta); } } } //---- Creating instances --------------------------------------------------------------------------- static TextEditNode create(TextEdit edit) { if (edit instanceof MoveTextEdit) return new MoveNode(edit); if (edit instanceof MoveTextEdit.TargetMark) return new TargetMarkNode(edit); return new DefaultNode(edit); } static RootNode createRoot(int length) { return new RootNode(length); } private TextEditNode(TextEdit edit) { fEdit= edit; } //---- Adding children --------------------------------------------------------------------------- protected void add(TextEditNode node) { if (fChildren == null) { fChildren= new ArrayList(1); node.fParent= this; fChildren.add(node); return; } // Optimize using binary search for (Iterator iter= fChildren.iterator(); iter.hasNext();) { TextEditNode child= (TextEditNode)iter.next(); if (child.covers(node)) { child.add(node); return; } } for (int i= 0; i < fChildren.size(); ) { TextEditNode child= (TextEditNode)fChildren.get(i); if (node.covers(child)) { fChildren.remove(i); node.add(child); } else { i++; } } node.fParent= this; fChildren.add(node); } public boolean covers(TextEditNode node) { return false; } //---- Accessing -------------------------------------------------------------------------------------- protected RootNode getRoot() { TextEditNode candidate= this; while(candidate.fParent != null) candidate= candidate.fParent; return (RootNode)candidate; } //---- Query interface -------------------------------------------------------------------------------- protected boolean isSynthetic() { return fEdit.isSynthetic; } public boolean isMove() { return false; } //---- Accessing Ranges ------------------------------------------------------------------------------ protected void checkRange(DocumentEvent event) { TextRange range= getTextRange(); int eventOffset= event.getOffset(); int eventLength= event.getLength(); int eventEnd = eventOffset + eventLength - 1; // "Edit changes text that lies outside its defined range" // Assert.isTrue(range.fOffset <= eventOffset && eventEnd <= range.getInclusiveEnd()); } protected TextRange getTextRange() { return fEdit.getTextRange(); } protected TextRange getChildRange() { return getTextRange(); } protected TextRange getParentRange() { return getTextRange(); } public boolean validate(int bufferLength) { if (fChildren == null) return true; // Only Moves and Nops can be parents if (!(fEdit instanceof MoveTextEdit || fEdit instanceof NopTextEdit)) return false; TextRange lastRange= null; for (Iterator iter= fChildren.iterator(); iter.hasNext(); ) { TextEditNode node= (TextEditNode)iter.next(); if (!node.validate(bufferLength)) return false; TextRange range= node.fEdit.getTextRange(); if (!range.isValid() || range.fOffset + range.fLength > bufferLength) return false; if (lastRange != null && !(range.isInsertionPointAt(lastRange.fOffset) || range.liesBehind(lastRange))) return false; lastRange= range; } return true; } //---- Updating ---------------------------------------------------------------------------------------- protected boolean activeNodeChanged(int delta) { TextRange range= getTextRange(); range.fLength+= delta; // we didn't adjust any processed nodes. return false; } protected void previousNodeChanged(int delta) { TextRange range= getTextRange(); range.fOffset+= delta; } protected void childNodeChanged(int delta) { getTextRange().fLength+= delta; } //---- Do it --------------------------------------------------------------------------------------------- protected void performDo(TextBuffer buffer, RangeUpdater updater, UndoMemento undo, IProgressMonitor pm) throws CoreException { int size= fChildren != null ? fChildren.size() : 0; for (int i= size - 1; i >= 0; i--) { TextEditNode child= (TextEditNode)fChildren.get(i); child.performDo(buffer, updater, undo, pm); } updater.setActiveNode(this); if (isSynthetic()) fEdit.perform(buffer); else undo.add(fEdit.perform(buffer)); pm.worked(1); } public void performedDo() { int size= fChildren != null ? fChildren.size() : 0; for (int i= size - 1; i >= 0; i--) { TextEditNode child= (TextEditNode)fChildren.get(i); child.performedDo(); } fEdit.performed(); } //---- Undo it ------------------------------------------------------------------------------------------- protected void performUndo(TextBuffer buffer, RangeUpdater updater, UndoMemento undo, IProgressMonitor pm) throws CoreException { int size= fChildren != null ? fChildren.size() : 0; for (int i= 0; i < size; i++) { setUndoIndex(i); TextEditNode child= (TextEditNode)fChildren.get(i); child.performUndo(buffer, updater, undo, pm); } updater.setActiveNode(this); if (isSynthetic()) fEdit.perform(buffer); else undo.add(fEdit.perform(buffer)); pm.worked(1); } protected void setUndoIndex(int index) { } public void performedUndo() { int size= fChildren != null ? fChildren.size() : 0; for (int i= 0; i < size; i++) { TextEditNode child= (TextEditNode)fChildren.get(i); child.performedUndo(); } fEdit.performed(); } // protected void createUndoList(List list) { // int size= fChildren != null ? fChildren.size() : 0; // for (int i= 0; i < size; i++) { // TextEditNode child= (TextEditNode)fChildren.get(i); // child.createUndoList(list); // } // list.add(this); // } } --- NEW FILE: TextBuffer.java --- /* * (c) Copyright IBM Corp. 2000, 2001. * All Rights Reserved. */ package org.epic.perleditor.templates.textmanipulation; import java.util.ArrayList; import java.util.List; //import net.sourceforge.phpdt.internal.corext.util.Strings; import org.epic.perleditor.templates.util.Strings; //import net.sourceforge.phpdt.internal.ui.PHPStatusConstants; import org.epic.perleditor.templates.ui.EPICStatusConstants; //import net.sourceforge.phpeclipse.PHPeclipsePlugin; import org.epic.perleditor.PerlEditorPlugin; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.DefaultLineTracker; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentListener; import org.eclipse.jface.text.ILineTracker; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.util.Assert; //import org.eclipse.jdt.internal.ui.JavaPlugin; //import org.eclipse.jdt.internal.ui.JavaStatusConstants; /** * An implementation of a <code>TextBuffer</code> that is based on <code>ITextSelection</code> * and <code>IDocument</code>. */ public class TextBuffer { private static class DocumentRegion extends TextRegion { IRegion fRegion; public DocumentRegion(IRegion region) { fRegion= region; } public int getOffset() { return fRegion.getOffset(); } public int getLength() { return fRegion.getLength(); } } public class Block { public String content; public int offsetDelta; } private IDocument fDocument; private static final TextBufferFactory fgFactory= new TextBufferFactory(); TextBuffer(IDocument document) { fDocument= document; Assert.isNotNull(fDocument); } /** * Returns the number of characters in this text buffer. * * @return the number of characters in this text buffer */ public int getLength() { return fDocument.getLength(); } /** * Returns the number of lines in this text buffer. * * @return the number of lines in this text buffer */ public int getNumberOfLines() { return fDocument.getNumberOfLines(); } /** * Returns the character at the given offset in this text buffer. * * @param offset a text buffer offset * @return the character at the offset * @exception IndexOutOfBoundsException if the <code>offset</code> * argument is negative or not less than the length of this text buffer. */ public char getChar(int offset) { try { return fDocument.getChar(offset); } catch (BadLocationException e) { throw new ArrayIndexOutOfBoundsException(e.getMessage()); } } /** * Returns the whole content of the text buffer. * * @return the whole content of the text buffer */ public String getContent() { return fDocument.get(); } /** * Returns length characters starting from the specified position. * * @return the characters specified by the given text region. Returns <code> * null</code> if text range is illegal */ public String getContent(int start, int length) { try { return fDocument.get(start, length); } catch (BadLocationException e) { return null; } } public Block getBlockContent(int start, int length, int tabWidth) { Block result= new Block(); StringBuffer buffer= new StringBuffer(); int lineOffset= getLineInformationOfOffset(start).getOffset(); if (start > lineOffset) { String line= getContent(lineOffset, start - lineOffset); String indent= Strings.getIndentString(line, tabWidth); result.offsetDelta= -indent.length(); buffer.append(indent); } final int end= start + length; TextRegion region= getLineInformationOfOffset(end); lineOffset= region.getOffset(); // Cursor is at beginning of next line if (lineOffset == end) { int lineNumber= getLineOfOffset(lineOffset); if (lineNumber > 0) { length= length - getLineDelimiter(lineNumber - 1).length(); } } if (buffer.length() == 0) { result.content= getContent(start, length); } else { buffer.append(getContent(start, length)); result.content= buffer.toString(); } return result; } /** * Returns the preferred line delimiter to be used for this text buffer. * * @return the preferred line delimiter */ public String getLineDelimiter() { String lineDelimiter= getLineDelimiter(0); if (lineDelimiter == null) lineDelimiter= System.getProperty("line.separator", "\n"); //$NON-NLS-1$ //$NON-NLS-2$ return lineDelimiter; } /** * Returns the line delimiter used for the given line number. Returns <code> * null</code> if the line number is out of range. * * @return the line delimiter used by the given line number or <code>null</code> */ public String getLineDelimiter(int line) { try { return fDocument.getLineDelimiter(line); } catch (BadLocationException e) { return null; } } /** * Returns the line for the given line number. If there isn't any line for * the given line number, <code>null</code> is returned. * * @return the line for the given line number or <code>null</code> */ public String getLineContent(int line) { try { IRegion region= fDocument.getLineInformation(line); return fDocument.get(region.getOffset(), region.getLength()); } catch (BadLocationException e) { return null; } } /** * Returns the line indent for the given line. If there isn't any line for the * given line number, <code>-1</code> is returned. * * @return the line indent for the given line number of <code>-1</code> */ public int getLineIndent(int lineNumber, int tabWidth) { return Strings.computeIndent(getLineContent(lineNumber), tabWidth); } /** * Returns a region of the specified line. The region contains the offset and the * length of the line excluding the line's delimiter. Returns <code>null</code> * if the line doesn't exist. * * @param line the line of interest * @return a line description or <code>null</code> if the given line doesn't * exist */ public TextRegion getLineInformation(int line) { try { return new DocumentRegion(fDocument.getLineInformation(line)); } catch (BadLocationException e) { return null; } } /** * Returns a line region of the specified offset. The region contains the offset and * the length of the line excluding the line's delimiter. Returns <code>null</code> * if the line doesn't exist. * * @param offset an offset into a line * @return a line description or <code>null</code> if the given line doesn't * exist */ public TextRegion getLineInformationOfOffset(int offset) { try { return new DocumentRegion(fDocument.getLineInformationOfOffset(offset)); } catch (BadLocationException e) { return null; } } /** * Returns the line number that contains the given position. If there isn't any * line that contains the position, <code>null</code> is returned. The returned * string is a copy and doesn't contain the line delimiter. * * @return the line that contains the given offset or <code>null</code> if line * doesn't exist */ public int getLineOfOffset(int offset) { try { return fDocument.getLineOfOffset(offset); } catch (BadLocationException e) { return -1; } } /** * Returns the line that contains the given position. If there isn't any * line that contains the position, <code>null</code> is returned. The returned * string is a copy and doesn't contain the line delimiter. * * @return the line that contains the given offset or <code>null</code> if line * doesn't exist */ public String getLineContentOfOffset(int offset) { try { IRegion region= fDocument.getLineInformationOfOffset(offset); return fDocument.get(region.getOffset(), region.getLength()); } catch (BadLocationException e) { return null; } } /** * Converts the text determined by the region [offset, length] into an array of lines. * The lines are copies of the original lines and don't contain any line delimiter * characters. * * @return the text converted into an array of strings. Returns <code>null</code> if the * region lies outside the source. */ public String[] convertIntoLines(int offset, int length, boolean lastNewLineCreateEmptyLine) { try { String text= fDocument.get(offset, length); ILineTracker tracker= new DefaultLineTracker(); tracker.set(text); int size= tracker.getNumberOfLines(); int lastLine= size - 1; List result= new ArrayList(size); for (int i= 0; i < size; i++) { IRegion region= tracker.getLineInformation(i); String line= getContent(offset + region.getOffset(), region.getLength()); if (i < lastLine || !"".equals(line) || lastNewLineCreateEmptyLine) //$NON-NLS-1$ result.add(line); } return (String[]) result.toArray(new String[result.size()]); } catch (BadLocationException e) { return null; } } /** * Subsitutes the given text for the specified text position * * @param offset the starting offset of the text to be replaced * @param length the length of the text to be replaced * @param text the substitution text * @exception CoreException if the text position [offset, length] is invalid. */ public void replace(int offset, int length, String text) throws CoreException { try { fDocument.replace(offset, length, text); } catch (BadLocationException e) { IStatus s = new Status(IStatus.ERROR, PerlEditorPlugin.getPluginId(), EPICStatusConstants.INTERNAL_ERROR, TextManipulationMessages.getFormattedString( "TextBuffer.wrongRange", //$NON-NLS-1$ new Object[] {new Integer(offset), new Integer(length) } ), e); throw new CoreException(s); } } public void replace(TextRange range, String text) throws CoreException { replace(range.fOffset, range.fLength, text); } //---- Special methods used by the <code>TextBufferEditor</code> /** * Releases this text buffer. */ /* package */ void release() { } /* package */ void registerUpdater(IDocumentListener listener) { fDocument.addDocumentListener(listener); } /* package */ void unregisterUpdater(IDocumentListener listener) { fDocument.removeDocumentListener(listener); } //---- Factory methods ---------------------------------------------------------------- /** * Acquires a text buffer for the given file. If a text buffer for the given * file already exists, then that one is returned. * * @param file the file for which a text buffer is requested * @return a managed text buffer for the given file * @exception CoreException if it was not possible to acquire the * text buffer */ public static TextBuffer acquire(IFile file) throws CoreException { return fgFactory.acquire(file); } /** * Releases the given text buffer. * * @param buffer the text buffer to be released */ public static void release(TextBuffer buffer) { fgFactory.release(buffer); } /** * Commits the changes made to the given text buffer to the underlying * storage system. * * @param buffer the text buffer containing the changes to be committed. * @param force if <code>true</code> the text buffer is committed in any case. * If <code>false</code> the text buffer is <b>ONLY</b> committed if the client * is the last one that holds a reference to the text buffer. Clients of this * method must make sure that they don't call this method from within an <code> * IWorkspaceRunnable</code>. * @param pm the progress monitor used to report progress if committing is * necessary */ public static void commitChanges(TextBuffer buffer, boolean force, IProgressMonitor pm) throws CoreException { fgFactory.commitChanges(buffer, force, pm); } /** * Creates a new <code>TextBuffer</code> for the given file. The returned * buffer will not be managed. Any subsequent call to <code>create</code> * with the same file will return a different text buffer. * <p> * If the file is currently open in a text editor, the editors content is copied into * the returned <code>TextBuffer</code>. Otherwise the content is read from * disk. * * @param file the file for which a text buffer is to be created * @return a new unmanaged text buffer * @exception CoreException if it was not possible to create the text buffer */ public static TextBuffer create(IFile file) throws CoreException { return fgFactory.create(file); } /** * Creates a new <code>TextBuffer</code> for the string. The returned * buffer will not be managed. Any subsequent call to <code>create</code> * with the identical string will return a different text buffer. * * @param content the text buffer's content * @return a new unmanaged text buffer */ public static TextBuffer create(String content) { return fgFactory.create(content); } // Unclear which methods are needed if we get the new save model. If optimal no // save is needed at all. public static void save(TextBuffer buffer, IProgressMonitor pm) throws CoreException { fgFactory.save(buffer, pm); } public static void aboutToChange(TextBuffer buffer) throws CoreException { fgFactory.aboutToChange(buffer); } public static void changed(TextBuffer buffer) throws CoreException { fgFactory.changed(buffer); } } --- NEW FILE: TextBufferEditor.java --- /* * (c) Copyright IBM Corp. 2000, 2001. * All Rights Reserved. */ package org.epic.perleditor.templates.textmanipulation; import java.util.ArrayList; import java.util.Arrays; import java.util.List; //import net.sourceforge.phpdt.internal.corext.textmanipulation.TextEditNode.RootNode; import org.epic.perleditor.templates.textmanipulation.TextEditNode.RootNode; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; //import org.eclipse.jdt.core.IJavaModelStatusConstants; //import org.eclipse.jdt.core.JavaModelException; // //import org.eclipse.jdt.internal.corext.Assert; /** * A <code>TextBufferEditor</code> manages a set of <code>TextEdit</code>s and applies * them as a whole to a <code>TextBuffer</code>. Added <code>TextEdit</code>s must * not overlap. The only exception from this rule are insertion point. There can be more than * one insert point at the same text position. Clients should use the method <code> * canPerformEdits</code> to validate if all added text edits follow these rules. * <p> * Clients can attach more than one <code>TextBufferEditor</code> to a single <code> * TextBuffer</code>. If so <code>canPerformEdits</code> validates all text edits from * all text buffer editors working on the same text buffer. */ public class TextBufferEditor { private TextBuffer fBuffer; private List fEdits; private RootNode fRootNode; private int fNumberOfNodes; private int fConnectCount; private int fMode; /* package */ static final int UNDEFINED= 0; /* package */ static final int REDO= 1; /* package */ static final int UNDO= 2; /** * Creates a new <code>TextBufferEditor</code> for the given * <code>TextBuffer</code>. * * @param the text buffer this editor is working on. */ public TextBufferEditor(TextBuffer buffer) { fBuffer= buffer; // Assert.isNotNull(fBuffer); fEdits= new ArrayList(); } /** * Returns the text buffer this editor is working on. * * @return the text buffer this editor is working on */ public TextBuffer getTextBuffer() { return fBuffer; } /** * Adds a <code>TextEdit</code> to this text editor. Adding a <code>TextEdit</code> * to a <code>TextBufferEditor</code> transfers ownership of the edit to the editor. So * after a edit has been added to a editor the creator of that edit <b>must</b> not continue * modifing it. * * @param edit the text edit to be added * @exception CoreException if the text edit can not be added * to this text buffer editor */ public void add(TextEdit edit) throws CoreException { // Assert.isTrue(fMode == UNDEFINED || fMode == REDO); internalAdd(edit); fMode= REDO; } /** * Adds a <code>MultiTextEdit</code> to this text editor. Adding a <code>MultiTextEdit</code> * to a <co... [truncated message content] |