17d515603ec4dc3e761c798a2ae9b15140122fdb
[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.8 2005-05-05 19:08:50 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 >= 0 && (right - left >= 0)) {
305         if (left != right || text != null && text.length() > 0) {
306           outerDocumentEvent = new DocumentEvent(outerDocument, left, right - left, text);
307
308           outerDocument.fireDocumentAboutToBeChanged(outerDocumentEvent);
309         }
310       }
311     }
312   }
313
314   protected int fixupPartitions(DocumentEvent event) {
315     int offset = event.getOffset();
316     int length = event.getLength();
317     int end = offset + length;
318
319     // fixup/notify inner views laying on change boundaries
320
321     int first = computeFlatNodeIndex(offset);
322     if (first > 0) {
323       FlatNode p = (FlatNode) nodes.get(first - 1);
324
325       int right = p.offset + p.length;
326       if (offset < right) {
327         // change overlaps with partition
328         if (end < right) {
329           // cahnge completely inside partition
330           String text = event.getText();
331           p.length -= length;
332           if (text != null) {
333             p.length += text.length();
334           }
335
336           if (p instanceof ViewNode) {
337             // TODO: revisit condition
338             InnerDocumentView innerDocument = ((ViewNode) p).view;
339             if (innerDocument != null) {
340               int start = innerDocument.getLocalOffset(offset);
341               innerDocument.fireDocumentChanged(new DocumentEvent(innerDocument, start, length, text));
342             }
343           }
344         } else {
345           // cut partition at right
346           int cut = p.offset + p.length - offset;
347           p.length -= cut;
348
349           if (p instanceof ViewNode) {
350             // TODO: revisit condition
351             InnerDocumentView innerDocument = ((ViewNode) p).view;
352             if (innerDocument != null) {
353               int start = innerDocument.getLocalOffset(offset);
354               // TODO: ???fireDocumentAboutToBeChanged???
355               innerDocument.fireDocumentChanged(new DocumentEvent(innerDocument, start, cut, null));
356             }
357           }
358         }
359       }
360     }
361
362     int last = computeFlatNodeIndex(end);
363     if (last > 0 && first != last) {
364       FlatNode p = (FlatNode) nodes.get(last - 1);
365
366       int right = p.offset + p.length;
367       if (end < right) {
368         // cut partition at left
369         int cut = end - p.offset;
370         p.length -= cut;
371         p.offset = offset;
372
373         String text = event.getText();
374         if (text != null) {
375           p.offset += text.length();
376         }
377
378         if (p instanceof ViewNode) {
379           // TODO: revisit condition
380           InnerDocumentView innerDocument = ((ViewNode) p).view;
381           if (innerDocument != null) {
382             // TODO: ???fireDocumentAboutToBeChanged???
383             innerDocument.fireDocumentChanged(new DocumentEvent(innerDocument, 0, cut, null));
384           }
385         }
386
387         --last;
388       }
389     }
390
391     // fixup inner views laying afrer change
392
393     String text = event.getText();
394     if (text != null) {
395       length -= text.length();
396     }
397
398     for (int i = last, size = nodes.size(); i < size; i++) {
399       ((FlatNode) nodes.get(i)).offset -= length;
400     }
401
402     // delete inner views laying completely inside change boundaries
403
404     if (first < last) {
405       do {
406         deleteInnerRegion((FlatNode) nodes.get(--last));
407       } while (first < last);
408
409       rememberRegion(offset, 0);
410     }
411
412     // notify outer view
413
414     if (outerDocumentEvent != null) {
415       outerDocument.fireDocumentChanged(outerDocumentEvent);
416     }
417
418     return first;
419   }
420
421   /*
422    * @see org.eclipse.jface.text.IDocumentPartitioner#computePartitioning(int, int)
423    */
424   protected String getContentType(String parent, String view) {
425     if (view != null) {
426       return view;
427     }
428
429     if (parent != null) {
430       return parent;
431     }
432
433     return IDocument.DEFAULT_CONTENT_TYPE;
434   }
435
436   /*
437    * @see org.eclipse.jface.text.IDocumentPartitioner#computePartitioning(int, int)
438    */
439   public ITypedRegion[] computePartitioning(int offset, int length) {
440     List list = new ArrayList();
441     //    if (DEBUG) {
442     //      System.out.print("MultiViewPartitioner::computePartitioning - Offset: ");
443     //      System.out.print(offset);
444     //      System.out.print(", Length: ");
445     //      System.out.print(length);
446     //      System.out.println("");
447     //    }
448     int end = offset + length;
449
450     int index = computeFlatNodeIndex(offset);
451     while (true) {
452       FlatNode prev = (index > 0) ? (FlatNode) nodes.get(index - 1) : null;
453
454       if (prev != null) {
455         if (prev.overlapsWith(offset, length)) {
456           addInnerPartitions(list, offset, length, prev);
457         }
458
459         if (end <= prev.offset + prev.length) {
460           break;
461         }
462       }
463
464       FlatNode next = (index < nodes.size()) ? (FlatNode) nodes.get(index) : null;
465
466       if (next == null || offset < next.offset) {
467         addOuterPartitions(list, offset, length, prev, next);
468       }
469
470       if (next == null) {
471         break;
472       }
473
474       ++index;
475     }
476     //    if (DEBUG) {
477     //      showList(list);
478     //    }
479
480     return (TypedRegion[]) list.toArray(new TypedRegion[list.size()]);
481   }
482
483   private void showList(List list) {
484     try {
485       throw new NullPointerException();
486     } catch (Exception e) {
487       e.printStackTrace();
488     }
489     System.out.println(">>>>>List start");
490     TypedRegion temp;
491     for (int i = 0; i < list.size(); i++) {
492       temp = (TypedRegion) list.get(i);
493       System.out.print("Offset: ");
494       System.out.print(temp.getOffset());
495       System.out.print(", Length: ");
496       System.out.print(temp.getLength());
497       System.out.print(", Type: ");
498       System.out.print(temp.getType());
499       System.out.println("");
500     }
501     System.out.println("<<<<<List end");
502   }
503
504   private void addOuterPartitions(List list, int offset, int length, FlatNode prev, FlatNode next) {
505     // limit region
506     int start = offset;
507     int end = offset + length;
508
509     if (prev != null && start < prev.offset + prev.length) {
510       start = prev.offset + prev.length;
511     }
512
513     if (next != null && next.offset < end) {
514       end = next.offset;
515     }
516
517     if (start == end) {
518       return;
519     }
520
521     if (outerDocument == null) {
522       //      if (DEBUG) {
523       //        if (end - start<0) {
524       //          throw new IndexOutOfBoundsException();
525       //        }
526       //      }
527       list.add(new TypedRegion(start, end - start, getContentType(null, IDocument.DEFAULT_CONTENT_TYPE)));
528       return;
529     }
530
531     try {
532       // convert to outer offsets
533       start = outerDocument.getLocalOffset(start);
534       end = outerDocument.getLocalOffset(end);
535       if (end - start >= 0) {//jsurfer insert line
536         ITypedRegion[] regions = outerDocument.computePartitioning(start, end - start);
537
538         for (int i = 0; i < regions.length; i++) {
539           ITypedRegion region = regions[i];
540
541           // convert back to parent offsets
542           start = outerDocument.getParentOffset(region.getOffset());
543           end = start + region.getLength();
544
545           if (prev != null) {
546             offset = prev.offset + prev.length;
547             if (start < offset) {
548               start = offset;
549             }
550           }
551
552           if (next != null) {
553             offset = next.offset;
554             if (offset < end) {
555               end = offset;
556             }
557           }
558           //          if (DEBUG) {
559           //            if (end - start<0) {
560           //              showList(list);
561           //              System.out.print("MultiViewPartitioner::addOuterPartitions - Offset: ");
562           //              System.out.print(offset);
563           //              System.out.print(", Start: ");
564           //              System.out.print(start);
565           //              System.out.print(", End: ");
566           //              System.out.print(end);
567           //              System.out.print(", Type: ");
568           //              System.out.print(region.getType());
569           //              System.out.println("");
570           //              throw new IndexOutOfBoundsException();
571           //            }
572           //          }
573           list.add(new TypedRegion(start, end - start, getContentType(null, region.getType())));
574         }
575       }
576     } catch (BadLocationException x) {
577     }
578   }
579
580   private void addInnerPartitions(List list, int offset, int length, FlatNode position) {
581     InnerDocumentView innerDocument = null;
582     if (position instanceof ViewNode) {
583       // TODO: revisit condition
584       innerDocument = ((ViewNode) position).view;
585     }
586
587     if (innerDocument == null) {
588       // simple partition
589       //      if (DEBUG) {
590       //        if (position.length<0) {
591       //          throw new IndexOutOfBoundsException();
592       //        }
593       //      }
594       list.add(new TypedRegion(position.offset, position.length, getContentType(position.type, null)));
595       return;
596     }
597
598     // multiplexing to inner view
599     try {
600       // limit region
601       int start = Math.max(offset, position.offset);
602       int end = Math.min(offset + length, position.offset + position.length);
603
604       // convert to document offsets
605       length = end - start;
606       offset = innerDocument.getLocalOffset(start);
607
608       ITypedRegion[] regions = innerDocument.computePartitioning(offset, length);
609
610       for (int i = 0; i < regions.length; i++) {
611         ITypedRegion region = regions[i];
612
613         // convert back to parent offsets
614         offset = innerDocument.getParentOffset(region.getOffset());
615         length = region.getLength();
616         //        if (DEBUG) {
617         //          if (length<0) {
618         //            throw new IndexOutOfBoundsException();
619         //          }
620         //        }
621         list.add(new TypedRegion(offset, length, getContentType(position.type, region.getType())));
622       }
623     } catch (BadLocationException x) {
624     }
625   }
626
627   /*
628    * @see org.eclipse.jface.text.IDocumentPartitioner#getPartition(int)
629    */
630   public ITypedRegion getPartition(int offset) {
631     if (nodes.size() == 0) {
632       return getOuterPartition(offset, null, null);
633     }
634
635     int index = computeFlatNodeIndex(offset);
636     if (index < nodes.size()) {
637       FlatNode next = (FlatNode) nodes.get(index);
638
639       if (offset == next.offset) {
640         return getInnerPartition(offset, next);
641       }
642
643       if (index == 0) {
644         return getOuterPartition(offset, null, next);
645       }
646
647       FlatNode prev = (FlatNode) nodes.get(index - 1);
648
649       if (prev.includes(offset)) {
650         return getInnerPartition(offset, prev);
651       }
652
653       return getOuterPartition(offset, prev, next);
654     }
655
656     FlatNode prev = (FlatNode) nodes.get(nodes.size() - 1);
657
658     if (prev.includes(offset)) {
659       return getInnerPartition(offset, prev);
660     }
661
662     return getOuterPartition(offset, prev, null);
663   }
664
665   protected ITypedRegion getOuterPartition(int offset, FlatNode prev, FlatNode next) {
666     try {
667       int start, end;
668       String type;
669
670       if (outerDocument == null) {
671         start = 0;
672         end = document.getLength();
673         type = getContentType(null, IDocument.DEFAULT_CONTENT_TYPE);
674       } else {
675         int outerOffset = outerDocument.getLocalOffset(offset);
676         //axelcl start
677         if (outerOffset < 0) {
678           start = 0;
679           end = document.getLength();
680           type = getContentType(null, IDocument.DEFAULT_CONTENT_TYPE);
681         } else {
682           //        axelcl end
683           ITypedRegion region = outerDocument.getPartition(outerOffset);
684
685           start = region.getOffset();
686           end = start + region.getLength();
687
688           // convert to parent offset
689           start = outerDocument.getParentOffset(start);
690           end = outerDocument.getParentOffset(end);
691
692           type = getContentType(null, region.getType());
693         }
694       }
695
696       if (prev != null) {
697         offset = prev.offset + prev.length;
698         if (start < offset) {
699           start = offset;
700         }
701       }
702
703       if (next != null) {
704         offset = next.offset;
705         if (offset < end) {
706           end = offset;
707         }
708       }
709
710       return new TypedRegion(start, end - start, type);
711     } catch (BadLocationException x) {
712       x.printStackTrace();
713       throw new IllegalArgumentException();
714     }
715   }
716
717   protected ITypedRegion getInnerPartition(int offset, FlatNode position) {
718     if (position instanceof ViewNode) {
719       // TODO: revisit condition
720       InnerDocumentView innerDocument = ((ViewNode) position).view;
721
722       if (innerDocument != null) {
723         // multiplexing to inner view
724         try {
725           // convert to inner offset
726           ITypedRegion region = innerDocument.getPartition(innerDocument.getLocalOffset(offset));
727
728           // convert to parent offset
729           offset = innerDocument.getParentOffset(region.getOffset());
730
731           return new TypedRegion(offset, region.getLength(), getContentType(position.type, region.getType()));
732         } catch (BadLocationException x) {
733         }
734       }
735     }
736
737     // simple partition
738     return new TypedRegion(position.offset, position.length, position.type);
739   }
740 }