Wrong partition length raises exception
[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.9 2005-05-13 20:17:31 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 //          if (length>=9400) {
443 //            length--;
444 //          }
445 //          System.out.print("MultiViewPartitioner::computePartitioning - Offset: ");
446 //          System.out.print(offset);
447 //          System.out.print(", Length: ");
448 //          System.out.print(length);
449 //          System.out.println("");
450 //        }
451     int end = offset + length;
452
453     int index = computeFlatNodeIndex(offset);
454     while (true) {
455       FlatNode prev = (index > 0) ? (FlatNode) nodes.get(index - 1) : null;
456
457       if (prev != null) {
458         if (prev.overlapsWith(offset, length)) {
459           addInnerPartitions(list, offset, length, prev);
460         }
461
462         if (end <= prev.offset + prev.length) {
463           break;
464         }
465       }
466
467       FlatNode next = (index < nodes.size()) ? (FlatNode) nodes.get(index) : null;
468
469       if (next == null || offset < next.offset) {
470         addOuterPartitions(list, offset, length, prev, next);
471       }
472
473       if (next == null) {
474         break;
475       }
476
477       ++index;
478     }
479     //    if (DEBUG) {
480     //      showList(list);
481     //    }
482
483     return (TypedRegion[]) list.toArray(new TypedRegion[list.size()]);
484   }
485
486   private void showList(List list) {
487     try {
488       throw new NullPointerException();
489     } catch (Exception e) {
490       e.printStackTrace();
491     }
492     System.out.println(">>>>>List start");
493     TypedRegion temp;
494     for (int i = 0; i < list.size(); i++) {
495       temp = (TypedRegion) list.get(i);
496       System.out.print("Offset: ");
497       System.out.print(temp.getOffset());
498       System.out.print(", Length: ");
499       System.out.print(temp.getLength());
500       System.out.print(", Type: ");
501       System.out.print(temp.getType());
502       System.out.println("");
503     }
504     System.out.println("<<<<<List end");
505   }
506
507   private void addOuterPartitions(List list, int offset, int length, FlatNode prev, FlatNode next) {
508     // limit region
509     int start = offset;
510     int end = offset + length;
511
512     if (prev != null && start < prev.offset + prev.length) {
513       start = prev.offset + prev.length;
514     }
515
516     if (next != null && next.offset < end) {
517       end = next.offset;
518     }
519
520     if (start == end) {
521       return;
522     }
523
524     if (outerDocument == null) {
525       //      if (DEBUG) {
526       //        if (end - start<0) {
527       //          throw new IndexOutOfBoundsException();
528       //        }
529       //      }
530       list.add(new TypedRegion(start, end - start, getContentType(null, IDocument.DEFAULT_CONTENT_TYPE)));
531       return;
532     }
533
534    try {
535       // convert to outer offsets
536       start = outerDocument.getLocalOffset(start);
537       end = outerDocument.getLocalOffset(end);
538       int len = end - start;
539       if (len >= 0) {        
540         ITypedRegion[] regions = null;
541         try {
542           regions = outerDocument.computePartitioning(start, len);
543         } catch (Exception e) {
544           // nasty workaround, which prevents cursor from moveing backwards in the editor
545           // but doesn't solve the partitioning problem
546           regions = new ITypedRegion[0];
547           System.out.println("MultiViewerPartitioner#addOuterPartitions failure\n"+"start:"+start +" length:" +len+"\n"+outerDocument.get(start,len));
548         }
549         for (int i = 0; i < regions.length; i++) {
550           ITypedRegion region = regions[i];
551
552           // convert back to parent offsets
553           start = outerDocument.getParentOffset(region.getOffset());
554           end = start + region.getLength();
555
556           if (prev != null) {
557             offset = prev.offset + prev.length;
558             if (start < offset) {
559               start = offset;
560             }
561           }
562
563           if (next != null) {
564             offset = next.offset;
565             if (offset < end) {
566               end = offset;
567             }
568           }
569           //          if (DEBUG) {
570           //            if (end - start<0) {
571           //              showList(list);
572           //              System.out.print("MultiViewPartitioner::addOuterPartitions - Offset: ");
573           //              System.out.print(offset);
574           //              System.out.print(", Start: ");
575           //              System.out.print(start);
576           //              System.out.print(", End: ");
577           //              System.out.print(end);
578           //              System.out.print(", Type: ");
579           //              System.out.print(region.getType());
580           //              System.out.println("");
581           //              throw new IndexOutOfBoundsException();
582           //            }
583           //          }
584           list.add(new TypedRegion(start, end - start, getContentType(null, region.getType())));
585         }
586       }
587     } catch (BadLocationException x) {
588     }
589   }
590
591   private void addInnerPartitions(List list, int offset, int length, FlatNode position) {
592     InnerDocumentView innerDocument = null;
593     if (position instanceof ViewNode) {
594       // TODO: revisit condition
595       innerDocument = ((ViewNode) position).view;
596     }
597
598     if (innerDocument == null) {
599       // simple partition
600       //      if (DEBUG) {
601       //        if (position.length<0) {
602       //          throw new IndexOutOfBoundsException();
603       //        }
604       //      }
605       list.add(new TypedRegion(position.offset, position.length, getContentType(position.type, null)));
606       return;
607     }
608
609     // multiplexing to inner view
610     try {
611       // limit region
612       int start = Math.max(offset, position.offset);
613       int end = Math.min(offset + length, position.offset + position.length);
614
615       // convert to document offsets
616       length = end - start;
617       offset = innerDocument.getLocalOffset(start);
618
619       ITypedRegion[] regions = innerDocument.computePartitioning(offset, length);
620
621       for (int i = 0; i < regions.length; i++) {
622         ITypedRegion region = regions[i];
623
624         // convert back to parent offsets
625         offset = innerDocument.getParentOffset(region.getOffset());
626         length = region.getLength();
627         //        if (DEBUG) {
628         //          if (length<0) {
629         //            throw new IndexOutOfBoundsException();
630         //          }
631         //        }
632         list.add(new TypedRegion(offset, length, getContentType(position.type, region.getType())));
633       }
634     } catch (BadLocationException x) {
635     }
636   }
637
638   /*
639    * @see org.eclipse.jface.text.IDocumentPartitioner#getPartition(int)
640    */
641   public ITypedRegion getPartition(int offset) {
642     if (nodes.size() == 0) {
643       return getOuterPartition(offset, null, null);
644     }
645
646     int index = computeFlatNodeIndex(offset);
647     if (index < nodes.size()) {
648       FlatNode next = (FlatNode) nodes.get(index);
649
650       if (offset == next.offset) {
651         return getInnerPartition(offset, next);
652       }
653
654       if (index == 0) {
655         return getOuterPartition(offset, null, next);
656       }
657
658       FlatNode prev = (FlatNode) nodes.get(index - 1);
659
660       if (prev.includes(offset)) {
661         return getInnerPartition(offset, prev);
662       }
663
664       return getOuterPartition(offset, prev, next);
665     }
666
667     FlatNode prev = (FlatNode) nodes.get(nodes.size() - 1);
668
669     if (prev.includes(offset)) {
670       return getInnerPartition(offset, prev);
671     }
672
673     return getOuterPartition(offset, prev, null);
674   }
675
676   protected ITypedRegion getOuterPartition(int offset, FlatNode prev, FlatNode next) {
677     try {
678       int start, end;
679       String type;
680
681       if (outerDocument == null) {
682         start = 0;
683         end = document.getLength();
684         type = getContentType(null, IDocument.DEFAULT_CONTENT_TYPE);
685       } else {
686         int outerOffset = outerDocument.getLocalOffset(offset);
687         //axelcl start
688         if (outerOffset < 0) {
689           start = 0;
690           end = document.getLength();
691           type = getContentType(null, IDocument.DEFAULT_CONTENT_TYPE);
692         } else {
693           //        axelcl end
694           ITypedRegion region = outerDocument.getPartition(outerOffset);
695
696           start = region.getOffset();
697           end = start + region.getLength();
698
699           // convert to parent offset
700           start = outerDocument.getParentOffset(start);
701           end = outerDocument.getParentOffset(end);
702
703           type = getContentType(null, region.getType());
704         }
705       }
706
707       if (prev != null) {
708         offset = prev.offset + prev.length;
709         if (start < offset) {
710           start = offset;
711         }
712       }
713
714       if (next != null) {
715         offset = next.offset;
716         if (offset < end) {
717           end = offset;
718         }
719       }
720
721       return new TypedRegion(start, end - start, type);
722     } catch (BadLocationException x) {
723       x.printStackTrace();
724       throw new IllegalArgumentException();
725     }
726   }
727
728   protected ITypedRegion getInnerPartition(int offset, FlatNode position) {
729     if (position instanceof ViewNode) {
730       // TODO: revisit condition
731       InnerDocumentView innerDocument = ((ViewNode) position).view;
732
733       if (innerDocument != null) {
734         // multiplexing to inner view
735         try {
736           // convert to inner offset
737           ITypedRegion region = innerDocument.getPartition(innerDocument.getLocalOffset(offset));
738
739           // convert to parent offset
740           offset = innerDocument.getParentOffset(region.getOffset());
741
742           return new TypedRegion(offset, region.getLength(), getContentType(position.type, region.getType()));
743         } catch (BadLocationException x) {
744         }
745       }
746     }
747
748     // simple partition
749     return new TypedRegion(position.offset, position.length, position.type);
750   }
751 }