newest feature.xml
[phpeclipse.git] / net.sourceforge.phpeclipse.ui / src / net / sourceforge / phpeclipse / ui / text / rules / MultiViewPartitioner.java
1 /*
2  * Copyright (c) 2002-2004 Widespace, OU and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Common Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://solareclipse.sourceforge.net/legal/cpl-v10.html
7  * 
8  * Contributors:
9  *     Igor Malinin - initial contribution
10  * 
11  * $Id: MultiViewPartitioner.java,v 1.7 2004-11-13 12:36:32 axelcl Exp $
12  */
13
14 package net.sourceforge.phpeclipse.ui.text.rules;
15
16 import java.util.ArrayList;
17 import java.util.List;
18
19 import org.eclipse.jface.text.Assert;
20 import org.eclipse.jface.text.BadLocationException;
21 import org.eclipse.jface.text.DocumentEvent;
22 import org.eclipse.jface.text.IDocument;
23 import org.eclipse.jface.text.IDocumentPartitioner;
24 import org.eclipse.jface.text.IDocumentPartitioningListener;
25 import org.eclipse.jface.text.IDocumentPartitioningListenerExtension;
26 import org.eclipse.jface.text.IRegion;
27 import org.eclipse.jface.text.ITypedRegion;
28 import org.eclipse.jface.text.TypedRegion;
29 import org.eclipse.jface.text.rules.IPartitionTokenScanner;
30
31 /**
32  * Advanced partitioner which maintains partitions as views to connected document. Views have own partitioners themselves. This
33  * class is designed as a base for complex partitioners such as for JSP, PHP, ASP, etc. languages.
34  * 
35  * @author Igor Malinin
36  */
37 public abstract class MultiViewPartitioner extends AbstractPartitioner {
38
39   class ViewListener implements IDocumentPartitioningListener, IDocumentPartitioningListenerExtension {
40
41     /*
42      * @see org.eclipse.jface.text.IDocumentPartitioningListener#documentPartitioningChanged(IDocument)
43      */
44     public void documentPartitioningChanged(IDocument document) {
45       IDocumentView view = (IDocumentView) document;
46
47       int start = view.getParentOffset(0);
48       int end = view.getParentOffset(view.getLength());
49
50       rememberRegion(start, end - start);
51     }
52
53     /*
54      * @see org.eclipse.jface.text.IDocumentPartitioningListenerExtension#documentPartitioningChanged(IDocument, IRegion)
55      */
56     public void documentPartitioningChanged(IDocument document, IRegion region) {
57       IDocumentView view = (IDocumentView) document;
58
59       int offset = region.getOffset();
60
61       int start = view.getParentOffset(offset);
62       int end = view.getParentOffset(offset + region.getLength());
63
64       rememberRegion(start, end - start);
65     }
66   }
67
68   private ViewListener viewListener = new ViewListener();
69
70   private OuterDocumentView outerDocument;
71
72   private DocumentEvent outerDocumentEvent;
73
74   public MultiViewPartitioner(IPartitionTokenScanner scanner) {
75     super(scanner);
76   }
77
78   public void setOuterPartitioner(IDocumentPartitioner partitioner) {
79     if (outerDocument == null) {
80       if (partitioner == null) {
81         return;
82       }
83
84       outerDocument = new OuterDocumentView(document, nodes);
85       outerDocument.addDocumentPartitioningListener(viewListener);
86     }
87
88     IDocumentPartitioner old = outerDocument.getDocumentPartitioner();
89     if (old != null) {
90       outerDocument.setDocumentPartitioner(null);
91       old.disconnect();
92     }
93
94     if (partitioner != null) {
95       partitioner.connect(outerDocument);
96     }
97
98     outerDocument.setDocumentPartitioner(partitioner);
99
100     if (partitioner == null) {
101       outerDocument.removeDocumentPartitioningListener(viewListener);
102       outerDocument = null;
103     }
104   }
105
106   /**
107    * Create subpartitioner.
108    * 
109    * @param contentType
110    *          name of inner partition or <code>null</code> for outer partition
111    */
112   protected abstract IDocumentPartitioner createPartitioner(String contentType);
113
114   protected void addInnerRegion(FlatNode position) {
115     if (outerDocument != null) {
116       if (DEBUG) {
117         Assert.isTrue(position.offset >= 0, Integer.toString(position.offset));
118       }
119       int outerOffset = outerDocument.getLocalOffset(position.offset);
120       // axelcl start
121       DocumentEvent event = null;
122       if (outerOffset>=0) {
123       // axelcl end
124         event = new DocumentEvent(outerDocument, outerOffset, position.length, null);
125
126         outerDocument.fireDocumentAboutToBeChanged(event);
127       }
128       super.addInnerRegion(position);
129 //    axelcl start
130       if (event != null) {
131       // axelcl end
132         outerDocument.fireDocumentChanged(event);
133       }
134     } else {
135       super.addInnerRegion(position);
136     }
137
138     if (position instanceof ViewNode) {
139       // TODO: revisit condition
140       IDocumentPartitioner partitioner = createPartitioner(position.type);
141       if (partitioner != null) {
142         InnerDocumentView innerDocument = new InnerDocumentView(document, (ViewNode) position);
143
144         ((ViewNode) position).view = innerDocument;
145
146         partitioner.connect(innerDocument);
147         innerDocument.setDocumentPartitioner(partitioner);
148         innerDocument.addDocumentPartitioningListener(viewListener);
149       }
150     }
151   }
152
153   protected void removeInnerRegion(FlatNode position) {
154     try {
155       if (outerDocument != null) {
156         DocumentEvent event = null;
157         if (position.offset >= 0 && position.length >= 0) {
158           int outerOffset = outerDocument.getLocalOffset(position.offset);
159           if (outerOffset > 0) {
160             event = new DocumentEvent(outerDocument, outerOffset, 0, document.get(position.offset, position.length));
161
162             outerDocument.fireDocumentAboutToBeChanged(event);
163           }
164         }
165         super.removeInnerRegion(position);
166         if (position.offset >= 0) {
167           if (event != null) {
168             outerDocument.fireDocumentChanged(event);
169           }
170         }
171       } else {
172         super.removeInnerRegion(position);
173       }
174
175       if (position instanceof ViewNode) {
176         // TODO: revisit condition
177         InnerDocumentView innerDocument = ((ViewNode) position).view;
178         if (innerDocument != null) {
179           IDocumentPartitioner partitioner = innerDocument.getDocumentPartitioner();
180
181           innerDocument.removeDocumentPartitioningListener(viewListener);
182           innerDocument.setDocumentPartitioner(null);
183           partitioner.disconnect();
184         }
185       }
186     } catch (BadLocationException e) {
187     }
188   }
189
190   protected void deleteInnerRegion(FlatNode position) {
191     super.deleteInnerRegion(position);
192
193     if (position instanceof ViewNode) {
194       // TODO: revisit condition
195       InnerDocumentView innerDocument = ((ViewNode) position).view;
196       if (innerDocument != null) {
197         IDocumentPartitioner partitioner = innerDocument.getDocumentPartitioner();
198
199         innerDocument.removeDocumentPartitioningListener(viewListener);
200         innerDocument.setDocumentPartitioner(null);
201         partitioner.disconnect();
202       }
203     }
204   }
205
206   public void connect(IDocument document) {
207     //          outerDocument = new OuterDocumentView(document, innerPositions);
208
209     super.connect(document);
210
211     setOuterPartitioner(createPartitioner(null));
212     //          IDocumentPartitioner partitioner =
213     //                  partitioner.connect(outerDocument);
214     //          outerDocument.setDocumentPartitioner(partitioner);
215     //          outerDocument.addDocumentPartitioningListener(viewListener);
216   }
217
218   public void disconnect() {
219     try {
220       if (outerDocument != null) {
221         outerDocument.removeDocumentPartitioningListener(viewListener);
222
223         IDocumentPartitioner partitioner = outerDocument.getDocumentPartitioner();
224
225         outerDocument.setDocumentPartitioner(null);
226         partitioner.disconnect();
227       }
228     } finally {
229       // TODO: cleanup listeners
230       outerDocument = null;
231     }
232   }
233
234   /*
235    * @see org.eclipse.jface.text.IDocumentPartitioner#documentAboutToBeChanged(DocumentEvent)
236    */
237   public void documentAboutToBeChanged(DocumentEvent event) {
238     super.documentAboutToBeChanged(event);
239
240     outerDocumentEvent = null;
241
242     int offset = event.getOffset();
243     int length = event.getLength();
244     int end = offset + length;
245
246     // find left partition
247     int first = computeFlatNodeIndex(offset);
248     if (first > 0) {
249       FlatNode p = (FlatNode) nodes.get(first - 1);
250
251       int right = p.offset + p.length;
252       if (offset < right) {
253         // change overlaps with partition
254         InnerDocumentView innerDocument = null;
255         if (p instanceof ViewNode) {
256           // TODO: revisit condition
257           innerDocument = ((ViewNode) p).view;
258         }
259
260         if (end < right) {
261           if (innerDocument != null) {
262             // cahnge completely inside partition
263             int start = innerDocument.getLocalOffset(offset);
264             innerDocument.fireDocumentAboutToBeChanged(new DocumentEvent(innerDocument, start, length, event.getText()));
265           }
266
267           return;
268         }
269
270         if (innerDocument != null) {
271           // cut partition at right
272           int start = innerDocument.getLocalOffset(offset);
273           innerDocument.fireDocumentAboutToBeChanged(new DocumentEvent(innerDocument, start, innerDocument.getLength() - start,
274               null));
275         }
276       }
277     }
278
279     // find right partition
280     int last = computeFlatNodeIndex(end);
281     if (last > 0) {
282       FlatNode p = (FlatNode) nodes.get(last - 1);
283
284       if (p instanceof ViewNode) {
285         // TODO: revisit condition
286         InnerDocumentView innerDocument = ((ViewNode) p).view;
287         if (innerDocument != null) {
288           int right = p.offset + p.length;
289           if (end < right) {
290             // cut partition at left
291             int cut = innerDocument.getLocalOffset(end);
292             innerDocument.fireDocumentAboutToBeChanged(new DocumentEvent(innerDocument, 0, cut, null));
293           }
294         }
295       }
296     }
297
298     if (outerDocument != null) {
299       int left = outerDocument.getLocalOffset(offset);
300       int right = outerDocument.getLocalOffset(end);
301
302       String text = event.getText();
303
304       if (left != right || text != null && text.length() > 0) {
305         outerDocumentEvent = new DocumentEvent(outerDocument, left, right - left, text);
306
307         outerDocument.fireDocumentAboutToBeChanged(outerDocumentEvent);
308       }
309     }
310   }
311
312   protected int fixupPartitions(DocumentEvent event) {
313     int offset = event.getOffset();
314     int length = event.getLength();
315     int end = offset + length;
316
317     // fixup/notify inner views laying on change boundaries
318
319     int first = computeFlatNodeIndex(offset);
320     if (first > 0) {
321       FlatNode p = (FlatNode) nodes.get(first - 1);
322
323       int right = p.offset + p.length;
324       if (offset < right) {
325         // change overlaps with partition
326         if (end < right) {
327           // cahnge completely inside partition
328           String text = event.getText();
329           p.length -= length;
330           if (text != null) {
331             p.length += text.length();
332           }
333
334           if (p instanceof ViewNode) {
335             // TODO: revisit condition
336             InnerDocumentView innerDocument = ((ViewNode) p).view;
337             if (innerDocument != null) {
338               int start = innerDocument.getLocalOffset(offset);
339               innerDocument.fireDocumentChanged(new DocumentEvent(innerDocument, start, length, text));
340             }
341           }
342         } else {
343           // cut partition at right
344           int cut = p.offset + p.length - offset;
345           p.length -= cut;
346
347           if (p instanceof ViewNode) {
348             // TODO: revisit condition
349             InnerDocumentView innerDocument = ((ViewNode) p).view;
350             if (innerDocument != null) {
351               int start = innerDocument.getLocalOffset(offset);
352               // TODO: ???fireDocumentAboutToBeChanged???
353               innerDocument.fireDocumentChanged(new DocumentEvent(innerDocument, start, cut, null));
354             }
355           }
356         }
357       }
358     }
359
360     int last = computeFlatNodeIndex(end);
361     if (last > 0 && first != last) {
362       FlatNode p = (FlatNode) nodes.get(last - 1);
363
364       int right = p.offset + p.length;
365       if (end < right) {
366         // cut partition at left
367         int cut = end - p.offset;
368         p.length -= cut;
369         p.offset = offset;
370
371         String text = event.getText();
372         if (text != null) {
373           p.offset += text.length();
374         }
375
376         if (p instanceof ViewNode) {
377           // TODO: revisit condition
378           InnerDocumentView innerDocument = ((ViewNode) p).view;
379           if (innerDocument != null) {
380             // TODO: ???fireDocumentAboutToBeChanged???
381             innerDocument.fireDocumentChanged(new DocumentEvent(innerDocument, 0, cut, null));
382           }
383         }
384
385         --last;
386       }
387     }
388
389     // fixup inner views laying afrer change
390
391     String text = event.getText();
392     if (text != null) {
393       length -= text.length();
394     }
395
396     for (int i = last, size = nodes.size(); i < size; i++) {
397       ((FlatNode) nodes.get(i)).offset -= length;
398     }
399
400     // delete inner views laying completely inside change boundaries
401
402     if (first < last) {
403       do {
404         deleteInnerRegion((FlatNode) nodes.get(--last));
405       } while (first < last);
406
407       rememberRegion(offset, 0);
408     }
409
410     // notify outer view
411
412     if (outerDocumentEvent != null) {
413       outerDocument.fireDocumentChanged(outerDocumentEvent);
414     }
415
416     return first;
417   }
418
419   /*
420    * @see org.eclipse.jface.text.IDocumentPartitioner#computePartitioning(int, int)
421    */
422   protected String getContentType(String parent, String view) {
423     if (view != null) {
424       return view;
425     }
426
427     if (parent != null) {
428       return parent;
429     }
430
431     return IDocument.DEFAULT_CONTENT_TYPE;
432   }
433
434   /*
435    * @see org.eclipse.jface.text.IDocumentPartitioner#computePartitioning(int, int)
436    */
437   public ITypedRegion[] computePartitioning(int offset, int length) {
438     List list = new ArrayList();
439 //    if (DEBUG) {
440 //      System.out.print("MultiViewPartitioner::computePartitioning - Offset: ");
441 //      System.out.print(offset);
442 //      System.out.print(", Length: ");
443 //      System.out.print(length);
444 //      System.out.println("");
445 //    }
446     int end = offset + length;
447
448     int index = computeFlatNodeIndex(offset);
449     while (true) {
450       FlatNode prev = (index > 0) ? (FlatNode) nodes.get(index - 1) : null;
451
452       if (prev != null) {
453         if (prev.overlapsWith(offset, length)) {
454           addInnerPartitions(list, offset, length, prev);
455         }
456
457         if (end <= prev.offset + prev.length) {
458           break;
459         }
460       }
461
462       FlatNode next = (index < nodes.size()) ? (FlatNode) nodes.get(index) : null;
463
464       if (next == null || offset < next.offset) {
465         addOuterPartitions(list, offset, length, prev, next);
466       }
467
468       if (next == null) {
469         break;
470       }
471
472       ++index;
473     }
474 //    if (DEBUG) {
475 //      showList(list);
476 //    }
477       
478     return (TypedRegion[]) list.toArray(new TypedRegion[list.size()]);
479   }
480
481   private void showList(List list) {
482     try { 
483       throw new NullPointerException();
484     } catch (Exception e){
485       e.printStackTrace();
486     }
487     System.out.println(">>>>>List start");
488     TypedRegion temp;
489     for (int i = 0; i < list.size(); i++) {
490       temp = (TypedRegion)list.get(i);
491       System.out.print("Offset: ");
492       System.out.print(temp.getOffset());
493       System.out.print(", Length: ");
494       System.out.print(temp.getLength());
495       System.out.print(", Type: ");
496       System.out.print(temp.getType());
497       System.out.println("");
498     }
499     System.out.println("<<<<<List end");
500   }
501   private void addOuterPartitions(List list, int offset, int length, FlatNode prev, FlatNode next) {
502     // limit region
503     int start = offset;
504     int end = offset + length;
505
506     if (prev != null && start < prev.offset + prev.length) {
507       start = prev.offset + prev.length;
508     }
509
510     if (next != null && next.offset < end) {
511       end = next.offset;
512     }
513
514     if (start == end) {
515       return;
516     }
517
518     if (outerDocument == null) {
519 //      if (DEBUG) {
520 //        if (end - start<0) {
521 //          throw new IndexOutOfBoundsException();
522 //        }
523 //      }
524       list.add(new TypedRegion(start, end - start, getContentType(null, IDocument.DEFAULT_CONTENT_TYPE)));
525       return;
526     }
527
528     try {
529       // convert to outer offsets
530       start = outerDocument.getLocalOffset(start);
531       end = outerDocument.getLocalOffset(end);
532       if (end - start >= 0) {//jsurfer insert line
533         ITypedRegion[] regions = outerDocument.computePartitioning(start, end - start);
534
535         for (int i = 0; i < regions.length; i++) {
536           ITypedRegion region = regions[i];
537
538           // convert back to parent offsets
539           start = outerDocument.getParentOffset(region.getOffset());
540           end = start + region.getLength();
541
542           if (prev != null) {
543             offset = prev.offset + prev.length;
544             if (start < offset) {
545               start = offset;
546             }
547           }
548
549           if (next != null) {
550             offset = next.offset;
551             if (offset < end) {
552               end = offset;
553             }
554           }
555 //          if (DEBUG) {
556 //            if (end - start<0) {
557 //              showList(list);
558 //              System.out.print("MultiViewPartitioner::addOuterPartitions - Offset: ");
559 //              System.out.print(offset);
560 //              System.out.print(", Start: ");
561 //              System.out.print(start);
562 //              System.out.print(", End: ");
563 //              System.out.print(end);
564 //              System.out.print(", Type: ");
565 //              System.out.print(region.getType());
566 //              System.out.println("");
567 //              throw new IndexOutOfBoundsException();
568 //            }
569 //          } 
570           list.add(new TypedRegion(start, end - start, getContentType(null, region.getType())));
571         }
572       }
573     } catch (BadLocationException x) {
574     }
575   }
576
577   private void addInnerPartitions(List list, int offset, int length, FlatNode position) {
578     InnerDocumentView innerDocument = null;
579     if (position instanceof ViewNode) {
580       // TODO: revisit condition  
581       innerDocument = ((ViewNode) position).view;
582     }
583
584     if (innerDocument == null) {
585       // simple partition
586 //      if (DEBUG) {
587 //        if (position.length<0) {
588 //          throw new IndexOutOfBoundsException();
589 //        }
590 //      }
591       list.add(new TypedRegion(position.offset, position.length, getContentType(position.type, null)));
592       return;
593     }
594
595     // multiplexing to inner view
596     try {
597       // limit region
598       int start = Math.max(offset, position.offset);
599       int end = Math.min(offset + length, position.offset + position.length);
600
601       // convert to document offsets
602       length = end - start;
603       offset = innerDocument.getLocalOffset(start);
604
605       ITypedRegion[] regions = innerDocument.computePartitioning(offset, length);
606
607       for (int i = 0; i < regions.length; i++) {
608         ITypedRegion region = regions[i];
609
610         // convert back to parent offsets
611         offset = innerDocument.getParentOffset(region.getOffset());
612         length = region.getLength();
613 //        if (DEBUG) {
614 //          if (length<0) {
615 //            throw new IndexOutOfBoundsException();
616 //          }
617 //        }
618         list.add(new TypedRegion(offset, length, getContentType(position.type, region.getType())));
619       }
620     } catch (BadLocationException x) {
621     }
622   }
623
624   /*
625    * @see org.eclipse.jface.text.IDocumentPartitioner#getPartition(int)
626    */
627   public ITypedRegion getPartition(int offset) {
628     if (nodes.size() == 0) {
629       return getOuterPartition(offset, null, null);
630     }
631
632     int index = computeFlatNodeIndex(offset);
633     if (index < nodes.size()) {
634       FlatNode next = (FlatNode) nodes.get(index);
635
636       if (offset == next.offset) {
637         return getInnerPartition(offset, next);
638       }
639
640       if (index == 0) {
641         return getOuterPartition(offset, null, next);
642       }
643
644       FlatNode prev = (FlatNode) nodes.get(index - 1);
645
646       if (prev.includes(offset)) {
647         return getInnerPartition(offset, prev);
648       }
649
650       return getOuterPartition(offset, prev, next);
651     }
652
653     FlatNode prev = (FlatNode) nodes.get(nodes.size() - 1);
654
655     if (prev.includes(offset)) {
656       return getInnerPartition(offset, prev);
657     }
658
659     return getOuterPartition(offset, prev, null);
660   }
661
662   protected ITypedRegion getOuterPartition(int offset, FlatNode prev, FlatNode next) {
663     try {
664       int start, end;
665       String type;
666
667       if (outerDocument == null) {
668         start = 0;
669         end = document.getLength();
670         type = getContentType(null, IDocument.DEFAULT_CONTENT_TYPE);
671       } else {
672         int outerOffset = outerDocument.getLocalOffset(offset);
673         //axelcl start
674         if (outerOffset < 0) {
675           start = 0;
676           end = document.getLength();
677           type = getContentType(null, IDocument.DEFAULT_CONTENT_TYPE);
678         } else {
679 //        axelcl end
680           ITypedRegion region = outerDocument.getPartition(outerOffset);
681
682           start = region.getOffset();
683           end = start + region.getLength();
684
685           // convert to parent offset
686           start = outerDocument.getParentOffset(start);
687           end = outerDocument.getParentOffset(end);
688
689           type = getContentType(null, region.getType());
690         }
691       }
692
693       if (prev != null) {
694         offset = prev.offset + prev.length;
695         if (start < offset) {
696           start = offset;
697         }
698       }
699
700       if (next != null) {
701         offset = next.offset;
702         if (offset < end) {
703           end = offset;
704         }
705       }
706
707       return new TypedRegion(start, end - start, type);
708     } catch (BadLocationException x) {
709       x.printStackTrace();
710       throw new IllegalArgumentException();
711     }
712   }
713
714   protected ITypedRegion getInnerPartition(int offset, FlatNode position) {
715     if (position instanceof ViewNode) {
716       // TODO: revisit condition
717       InnerDocumentView innerDocument = ((ViewNode) position).view;
718
719       if (innerDocument != null) {
720         // multiplexing to inner view
721         try {
722           // convert to inner offset
723           ITypedRegion region = innerDocument.getPartition(innerDocument.getLocalOffset(offset));
724
725           // convert to parent offset
726           offset = innerDocument.getParentOffset(region.getOffset());
727
728           return new TypedRegion(offset, region.getLength(), getContentType(position.type, region.getType()));
729         } catch (BadLocationException x) {
730         }
731       }
732     }
733
734     // simple partition
735     return new TypedRegion(position.offset, position.length, position.type);
736   }
737 }