2 * (c) Copyright IBM Corp. 2000, 2001.
5 package net.sourceforge.phpdt.internal.corext.textmanipulation;
7 import java.util.ArrayList;
8 import java.util.Iterator;
11 import org.eclipse.core.runtime.CoreException;
12 import org.eclipse.core.runtime.IProgressMonitor;
13 import org.eclipse.jface.text.DocumentEvent;
14 import org.eclipse.jface.text.IDocumentListener;
18 * A helper class to arrange <code>TextEdit</code>s into a tree to optimize their
21 /* package */ abstract class TextEditNode {
23 /* package */ TextEditNode fParent;
24 /* package */ List fChildren;
25 /* package */ TextEdit fEdit;
27 /* package */ static class DefaultNode extends TextEditNode {
28 public DefaultNode(TextEdit edit) {
33 /* package */ static class RootNode extends TextEditNode {
34 private int fUndoIndex;
35 public RootNode(int length) {
36 super(new NopTextEdit(new TextRange(0, length)));
37 fEdit.isSynthetic= true;
39 public boolean covers(TextEditNode node) {
42 public UndoMemento performDo(TextBuffer buffer, IProgressMonitor pm) throws CoreException {
43 DoRangeUpdater updater= new DoRangeUpdater();
44 UndoMemento undo= new UndoMemento(TextBufferEditor.UNDO);
46 buffer.registerUpdater(updater);
47 performDo(buffer, updater, undo, pm);
49 buffer.unregisterUpdater(updater);
50 updater.setActiveNode(null);
54 public UndoMemento performUndo(TextBuffer buffer, IProgressMonitor pm) throws CoreException {
55 UndoRangeUpdater updater= new UndoRangeUpdater(this);
56 UndoMemento undo= new UndoMemento(TextBufferEditor.REDO);
58 buffer.registerUpdater(updater);
59 performUndo(buffer, updater, undo, pm);
61 buffer.unregisterUpdater(updater);
62 updater.setActiveNode(null);
67 protected void setUndoIndex(int index) {
71 protected int getUndoIndex() {
76 /* package */ abstract static class AbstractMoveNode extends TextEditNode {
79 private int fTargetIndex;
80 private int fSourceIndex;
82 private List fAffectedChildren;
84 public AbstractMoveNode(TextEdit edit) {
88 protected abstract TextRange getSourceRange();
89 protected abstract TextRange getTargetRange();
90 protected abstract boolean isUpMove();
91 protected boolean isDownMove() {
94 public boolean isMove() {
97 protected void checkRange(DocumentEvent event) {
98 TextRange range= getChildRange();
99 int eventOffset= event.getOffset();
100 int eventLength= event.getLength();
101 int eventEnd = eventOffset + eventLength - 1;
102 // "Edit changes text that lies outside its defined range"
103 // Assert.isTrue(range.fOffset <= eventOffset && eventEnd <= range.getInclusiveEnd());
105 protected boolean activeNodeChanged(int delta) {
106 TextRange targetRange= getTargetRange();
107 TextRange sourceRange= getSourceRange();
109 case 0: // the move delete
111 // Assert.isTrue(Math.abs(delta) == sourceRange.fLength);
113 updateOffset(fAffectedChildren, delta);
114 targetRange.fOffset+= delta;
116 sourceRange.fLength= 0;
120 TextEditNode target= (TextEditNode)fParent.fChildren.get(fTargetIndex);
121 TextEditNode source= (TextEditNode)fParent.fChildren.get(fSourceIndex);
122 updateOffset(source.fChildren, targetRange.fOffset - sourceRange.fOffset);
123 target.fChildren= source.fChildren;
124 if (target.fChildren != null) {
125 for (Iterator iter= target.fChildren.iterator(); iter.hasNext();) {
126 ((TextEditNode)iter.next()).fParent= target;
129 source.fChildren= null;
131 updateOffset(fAffectedChildren, delta);
132 sourceRange.fOffset+= delta;
134 targetRange.fLength= delta;
140 private static void updateOffset(List nodes, int delta) {
143 for (int i= nodes.size() - 1; i >= 0; i--) {
144 TextEditNode node= (TextEditNode)nodes.get(i);
145 TextRange range= node.getTextRange();
146 range.fOffset+= delta;
147 updateOffset(node.fChildren, delta);
150 private void init() {
151 TextRange source= getSourceRange();
152 TextRange target= getTargetRange();
153 List children= fParent.fChildren;
154 for (int i= children.size() - 1; i >= 0; i--) {
155 TextEditNode child= (TextEditNode)children.get(i);
156 TextRange range= child.fEdit.getTextRange();
159 else if (range == target)
162 int start= Math.min(fTargetIndex, fSourceIndex);
163 int end= Math.max(fTargetIndex, fSourceIndex);
164 fAffectedChildren= new ArrayList(3);
165 for (int i= start + 1; i < end; i++) {
166 fAffectedChildren.add(children.get(i));
169 private void reset() {
176 /* package */ static class MoveNode extends AbstractMoveNode {
177 public MoveNode(TextEdit edit) {
180 protected TextRange getChildRange() {
181 return ((MoveTextEdit)fEdit).getChildRange();
183 protected TextRange getSourceRange() {
184 return ((MoveTextEdit)fEdit).getSourceRange();
186 protected TextRange getTargetRange() {
187 return ((MoveTextEdit)fEdit).getTargetRange();
189 protected boolean isUpMove() {
190 return ((MoveTextEdit)fEdit).isUpMove();
192 public boolean isMovePartner(TextEditNode other) {
193 if (!(other instanceof TargetMarkNode))
195 return fEdit == ((MoveTextEdit.TargetMark)other.fEdit).getMoveTextEdit();
197 public boolean covers(TextEditNode node) {
198 if (node instanceof TargetMarkNode) {
199 MoveTextEdit.TargetMark edit= (MoveTextEdit.TargetMark)node.fEdit;
200 if (edit.getMoveTextEdit() == fEdit)
203 return getParentRange().covers(node.getChildRange());
207 /* package */ static class TargetMarkNode extends AbstractMoveNode {
208 public TargetMarkNode(TextEdit edit) {
211 protected TextRange getChildRange() {
212 return ((MoveTextEdit.TargetMark)fEdit).getMoveTextEdit().getChildRange();
214 protected TextRange getSourceRange() {
215 return ((MoveTextEdit.TargetMark)fEdit).getMoveTextEdit().getSourceRange();
217 protected TextRange getTargetRange() {
218 return ((MoveTextEdit.TargetMark)fEdit).getMoveTextEdit().getTargetRange();
220 protected boolean isUpMove() {
221 return ((MoveTextEdit.TargetMark)fEdit).getMoveTextEdit().isUpMove();
223 public boolean isMovePartner(TextEditNode other) {
224 return ((MoveTextEdit.TargetMark)fEdit).getMoveTextEdit() == other.fEdit;
228 //---- Range updating ---------------------------------------------------------------------------
230 private static abstract class RangeUpdater implements IDocumentListener {
231 protected TextEditNode fActiveNode;
232 public void documentAboutToBeChanged(DocumentEvent event) {
234 public void setActiveNode(TextEditNode node) {
237 public void updateParents(int delta) {
238 TextEditNode node= fActiveNode.fParent;
239 while (node != null) {
240 node.childNodeChanged(delta);
244 public static int getDelta(DocumentEvent event) {
245 return (event.getText() == null ? 0 : event.getText().length()) - event.getLength();
248 private static class DoRangeUpdater extends RangeUpdater {
249 private List fProcessedNodes= new ArrayList(10);
250 public void setActiveNode(TextEditNode node) {
251 if (fActiveNode != null)
252 fProcessedNodes.add(fActiveNode);
253 super.setActiveNode(node);
255 public void documentChanged(DocumentEvent event) {
256 fActiveNode.checkRange(event);
257 int delta= getDelta(event);
258 if (!fActiveNode.activeNodeChanged(delta)) {
259 for (Iterator iter= fProcessedNodes.iterator(); iter.hasNext();) {
260 ((TextEditNode)iter.next()).previousNodeChanged(delta);
263 updateParents(delta);
266 private static class UndoRangeUpdater extends RangeUpdater {
267 private RootNode fRootNode;
268 public UndoRangeUpdater(RootNode root) {
271 public void setActiveNode(TextEditNode node) {
272 super.setActiveNode(node);
274 public void documentChanged(DocumentEvent event) {
275 fActiveNode.checkRange(event);
276 int delta= getDelta(event);
277 if (!fActiveNode.activeNodeChanged(delta)) {
278 int start= fRootNode.getUndoIndex() + 1;
279 List children= fRootNode.fChildren;
280 int size= children != null ? children.size() : 0;
281 for (int i= start; i < size; i++) {
282 updateUndo((TextEditNode)children.get(i), delta);
285 updateParents(delta);
287 private void updateUndo(TextEditNode node, int delta) {
288 node.previousNodeChanged(delta);
289 List children= node.fChildren;
290 int size= children != null ? children.size() : 0;
291 for (int i= 0; i < size; i++) {
292 updateUndo((TextEditNode)children.get(i), delta);
297 //---- Creating instances ---------------------------------------------------------------------------
299 static TextEditNode create(TextEdit edit) {
300 if (edit instanceof MoveTextEdit)
301 return new MoveNode(edit);
302 if (edit instanceof MoveTextEdit.TargetMark)
303 return new TargetMarkNode(edit);
304 return new DefaultNode(edit);
307 static RootNode createRoot(int length) {
308 return new RootNode(length);
311 private TextEditNode(TextEdit edit) {
315 //---- Adding children ---------------------------------------------------------------------------
317 protected void add(TextEditNode node) {
318 if (fChildren == null) {
319 fChildren= new ArrayList(1);
324 // Optimize using binary search
325 for (Iterator iter= fChildren.iterator(); iter.hasNext();) {
326 TextEditNode child= (TextEditNode)iter.next();
327 if (child.covers(node)) {
332 for (int i= 0; i < fChildren.size(); ) {
333 TextEditNode child= (TextEditNode)fChildren.get(i);
334 if (node.covers(child)) {
345 public boolean covers(TextEditNode node) {
349 //---- Accessing --------------------------------------------------------------------------------------
351 protected RootNode getRoot() {
352 TextEditNode candidate= this;
353 while(candidate.fParent != null)
354 candidate= candidate.fParent;
355 return (RootNode)candidate;
358 //---- Query interface --------------------------------------------------------------------------------
360 protected boolean isSynthetic() {
361 return fEdit.isSynthetic;
364 public boolean isMove() {
368 //---- Accessing Ranges ------------------------------------------------------------------------------
370 protected void checkRange(DocumentEvent event) {
371 TextRange range= getTextRange();
372 int eventOffset= event.getOffset();
373 int eventLength= event.getLength();
374 int eventEnd = eventOffset + eventLength - 1;
375 // "Edit changes text that lies outside its defined range"
376 // Assert.isTrue(range.fOffset <= eventOffset && eventEnd <= range.getInclusiveEnd());
379 protected TextRange getTextRange() {
380 return fEdit.getTextRange();
383 protected TextRange getChildRange() {
384 return getTextRange();
387 protected TextRange getParentRange() {
388 return getTextRange();
391 public boolean validate(int bufferLength) {
392 if (fChildren == null)
394 // Only Moves and Nops can be parents
395 if (!(fEdit instanceof MoveTextEdit || fEdit instanceof NopTextEdit))
397 TextRange lastRange= null;
398 for (Iterator iter= fChildren.iterator(); iter.hasNext(); ) {
399 TextEditNode node= (TextEditNode)iter.next();
400 if (!node.validate(bufferLength))
402 TextRange range= node.fEdit.getTextRange();
403 if (!range.isValid() || range.fOffset + range.fLength > bufferLength)
405 if (lastRange != null && !(range.isInsertionPointAt(lastRange.fOffset) || range.liesBehind(lastRange)))
412 //---- Updating ----------------------------------------------------------------------------------------
414 protected boolean activeNodeChanged(int delta) {
415 TextRange range= getTextRange();
416 range.fLength+= delta;
417 // we didn't adjust any processed nodes.
421 protected void previousNodeChanged(int delta) {
422 TextRange range= getTextRange();
423 range.fOffset+= delta;
426 protected void childNodeChanged(int delta) {
427 getTextRange().fLength+= delta;
430 //---- Do it ---------------------------------------------------------------------------------------------
432 protected void performDo(TextBuffer buffer, RangeUpdater updater, UndoMemento undo, IProgressMonitor pm) throws CoreException {
433 int size= fChildren != null ? fChildren.size() : 0;
434 for (int i= size - 1; i >= 0; i--) {
435 TextEditNode child= (TextEditNode)fChildren.get(i);
436 child.performDo(buffer, updater, undo, pm);
438 updater.setActiveNode(this);
440 fEdit.perform(buffer);
442 undo.add(fEdit.perform(buffer));
446 public void performedDo() {
447 int size= fChildren != null ? fChildren.size() : 0;
448 for (int i= size - 1; i >= 0; i--) {
449 TextEditNode child= (TextEditNode)fChildren.get(i);
455 //---- Undo it -------------------------------------------------------------------------------------------
457 protected void performUndo(TextBuffer buffer, RangeUpdater updater, UndoMemento undo, IProgressMonitor pm) throws CoreException {
458 int size= fChildren != null ? fChildren.size() : 0;
459 for (int i= 0; i < size; i++) {
461 TextEditNode child= (TextEditNode)fChildren.get(i);
462 child.performUndo(buffer, updater, undo, pm);
464 updater.setActiveNode(this);
466 fEdit.perform(buffer);
468 undo.add(fEdit.perform(buffer));
472 protected void setUndoIndex(int index) {
475 public void performedUndo() {
476 int size= fChildren != null ? fChildren.size() : 0;
477 for (int i= 0; i < size; i++) {
478 TextEditNode child= (TextEditNode)fChildren.get(i);
479 child.performedUndo();
484 // protected void createUndoList(List list) {
485 // int size= fChildren != null ? fChildren.size() : 0;
486 // for (int i= 0; i < size; i++) {
487 // TextEditNode child= (TextEditNode)fChildren.get(i);
488 // child.createUndoList(list);