new feature module
[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.2 2004-09-22 18:51:51 jsurfer 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       DocumentEvent event = new DocumentEvent(outerDocument, outerDocument.getLocalOffset(position.offset), position.length, null);
120
121       outerDocument.fireDocumentAboutToBeChanged(event);
122       super.addInnerRegion(position);
123       outerDocument.fireDocumentChanged(event);
124     } else {
125       super.addInnerRegion(position);
126     }
127
128     if (position instanceof ViewNode) {
129       // TODO: revisit condition
130       IDocumentPartitioner partitioner = createPartitioner(position.type);
131       if (partitioner != null) {
132         InnerDocumentView innerDocument = new InnerDocumentView(document, (ViewNode) position);
133
134         ((ViewNode) position).view = innerDocument;
135
136         partitioner.connect(innerDocument);
137         innerDocument.setDocumentPartitioner(partitioner);
138         innerDocument.addDocumentPartitioningListener(viewListener);
139       }
140     }
141   }
142
143   protected void removeInnerRegion(FlatNode position) {
144     try {
145       if (outerDocument != null) {
146         DocumentEvent event = null;
147         if (position.offset >= 0) {
148           event = new DocumentEvent(outerDocument, outerDocument.getLocalOffset(position.offset), 0, document.get(position.offset,
149               position.length));
150
151           outerDocument.fireDocumentAboutToBeChanged(event);
152         }
153         super.removeInnerRegion(position);
154         if (position.offset >= 0) {
155           outerDocument.fireDocumentChanged(event);
156         }
157       } else {
158         super.removeInnerRegion(position);
159       }
160
161       if (position instanceof ViewNode) {
162         // TODO: revisit condition
163         InnerDocumentView innerDocument = ((ViewNode) position).view;
164         if (innerDocument != null) {
165           IDocumentPartitioner partitioner = innerDocument.getDocumentPartitioner();
166
167           innerDocument.removeDocumentPartitioningListener(viewListener);
168           innerDocument.setDocumentPartitioner(null);
169           partitioner.disconnect();
170         }
171       }
172     } catch (BadLocationException e) {
173     }
174   }
175
176   protected void deleteInnerRegion(FlatNode position) {
177     super.deleteInnerRegion(position);
178
179     if (position instanceof ViewNode) {
180       // TODO: revisit condition
181       InnerDocumentView innerDocument = ((ViewNode) position).view;
182       if (innerDocument != null) {
183         IDocumentPartitioner partitioner = innerDocument.getDocumentPartitioner();
184
185         innerDocument.removeDocumentPartitioningListener(viewListener);
186         innerDocument.setDocumentPartitioner(null);
187         partitioner.disconnect();
188       }
189     }
190   }
191
192   public void connect(IDocument document) {
193     //          outerDocument = new OuterDocumentView(document, innerPositions);
194
195     super.connect(document);
196
197     setOuterPartitioner(createPartitioner(null));
198     //          IDocumentPartitioner partitioner =
199     //                  partitioner.connect(outerDocument);
200     //          outerDocument.setDocumentPartitioner(partitioner);
201     //          outerDocument.addDocumentPartitioningListener(viewListener);
202   }
203
204   public void disconnect() {
205     try {
206       if (outerDocument != null) {
207         outerDocument.removeDocumentPartitioningListener(viewListener);
208
209         IDocumentPartitioner partitioner = outerDocument.getDocumentPartitioner();
210
211         outerDocument.setDocumentPartitioner(null);
212         partitioner.disconnect();
213       }
214     } finally {
215       // TODO: cleanup listeners
216       outerDocument = null;
217     }
218   }
219
220   /*
221    * @see org.eclipse.jface.text.IDocumentPartitioner#documentAboutToBeChanged(DocumentEvent)
222    */
223   public void documentAboutToBeChanged(DocumentEvent event) {
224     super.documentAboutToBeChanged(event);
225
226     outerDocumentEvent = null;
227
228     int offset = event.getOffset();
229     int length = event.getLength();
230     int end = offset + length;
231
232     // find left partition
233     int first = computeFlatNodeIndex(offset);
234     if (first > 0) {
235       FlatNode p = (FlatNode) nodes.get(first - 1);
236
237       int right = p.offset + p.length;
238       if (offset < right) {
239         // change overlaps with partition
240         InnerDocumentView innerDocument = null;
241         if (p instanceof ViewNode) {
242           // TODO: revisit condition
243           innerDocument = ((ViewNode) p).view;
244         }
245
246         if (end < right) {
247           if (innerDocument != null) {
248             // cahnge completely inside partition
249             int start = innerDocument.getLocalOffset(offset);
250             innerDocument.fireDocumentAboutToBeChanged(new DocumentEvent(innerDocument, start, length, event.getText()));
251           }
252
253           return;
254         }
255
256         if (innerDocument != null) {
257           // cut partition at right
258           int start = innerDocument.getLocalOffset(offset);
259           innerDocument.fireDocumentAboutToBeChanged(new DocumentEvent(innerDocument, start, innerDocument.getLength() - start,
260               null));
261         }
262       }
263     }
264
265     // find right partition
266     int last = computeFlatNodeIndex(end);
267     if (last > 0) {
268       FlatNode p = (FlatNode) nodes.get(last - 1);
269
270       if (p instanceof ViewNode) {
271         // TODO: revisit condition
272         InnerDocumentView innerDocument = ((ViewNode) p).view;
273         if (innerDocument != null) {
274           int right = p.offset + p.length;
275           if (end < right) {
276             // cut partition at left
277             int cut = innerDocument.getLocalOffset(end);
278             innerDocument.fireDocumentAboutToBeChanged(new DocumentEvent(innerDocument, 0, cut, null));
279           }
280         }
281       }
282     }
283
284     if (outerDocument != null) {
285       int left = outerDocument.getLocalOffset(offset);
286       int right = outerDocument.getLocalOffset(end);
287
288       String text = event.getText();
289
290       if (left != right || text != null && text.length() > 0) {
291         outerDocumentEvent = new DocumentEvent(outerDocument, left, right - left, text);
292
293         outerDocument.fireDocumentAboutToBeChanged(outerDocumentEvent);
294       }
295     }
296   }
297
298   protected int fixupPartitions(DocumentEvent event) {
299     int offset = event.getOffset();
300     int length = event.getLength();
301     int end = offset + length;
302
303     // fixup/notify inner views laying on change boundaries
304
305     int first = computeFlatNodeIndex(offset);
306     if (first > 0) {
307       FlatNode p = (FlatNode) nodes.get(first - 1);
308
309       int right = p.offset + p.length;
310       if (offset < right) {
311         // change overlaps with partition
312         if (end < right) {
313           // cahnge completely inside partition
314           String text = event.getText();
315           p.length -= length;
316           if (text != null) {
317             p.length += text.length();
318           }
319
320           if (p instanceof ViewNode) {
321             // TODO: revisit condition
322             InnerDocumentView innerDocument = ((ViewNode) p).view;
323             if (innerDocument != null) { 
324               int start = innerDocument.getLocalOffset(offset);
325               innerDocument.fireDocumentChanged(new DocumentEvent(innerDocument, start, length, text));
326             }
327           }
328         } else {
329           // cut partition at right
330           int cut = p.offset + p.length - offset;
331           p.length -= cut;
332
333           if (p instanceof ViewNode) {
334             // TODO: revisit condition
335             InnerDocumentView innerDocument = ((ViewNode) p).view;
336             if (innerDocument != null) {
337               int start = innerDocument.getLocalOffset(offset);
338               // TODO: ???fireDocumentAboutToBeChanged???
339               innerDocument.fireDocumentChanged(new DocumentEvent(innerDocument, start, cut, null));
340             }
341           }
342         }
343       }
344     }
345
346     int last = computeFlatNodeIndex(end);
347     if (last > 0 && first != last) {
348       FlatNode p = (FlatNode) nodes.get(last - 1);
349
350       int right = p.offset + p.length;
351       if (end < right) {
352         // cut partition at left
353         int cut = end - p.offset;
354         p.length -= cut;
355         p.offset = offset;
356
357         String text = event.getText();
358         if (text != null) {
359           p.offset += text.length();
360         }
361
362         if (p instanceof ViewNode) {
363           // TODO: revisit condition
364           InnerDocumentView innerDocument = ((ViewNode) p).view;
365           if (innerDocument != null) {
366             // TODO: ???fireDocumentAboutToBeChanged???
367             innerDocument.fireDocumentChanged(new DocumentEvent(innerDocument, 0, cut, null));
368           }
369         }
370
371         --last;
372       }
373     }
374
375     // fixup inner views laying afrer change
376
377     String text = event.getText();
378     if (text != null) {
379       length -= text.length();
380     }
381
382     for (int i = last, size = nodes.size(); i < size; i++) {
383       ((FlatNode) nodes.get(i)).offset -= length;
384     }
385
386     // delete inner views laying completely inside change boundaries
387
388     if (first < last) {
389       do {
390         deleteInnerRegion((FlatNode) nodes.get(--last));
391       } while (first < last);
392
393       rememberRegion(offset, 0);
394     }
395
396     // notify outer view
397
398     if (outerDocumentEvent != null) {
399       outerDocument.fireDocumentChanged(outerDocumentEvent);
400     }
401
402     return first;
403   }
404
405   /*
406    * @see org.eclipse.jface.text.IDocumentPartitioner#computePartitioning(int, int)
407    */
408   protected String getContentType(String parent, String view) {
409     if (view != null) {
410       return view;
411     }
412
413     if (parent != null) {
414       return parent;
415     }
416
417     return IDocument.DEFAULT_CONTENT_TYPE;
418   }
419
420   /*
421    * @see org.eclipse.jface.text.IDocumentPartitioner#computePartitioning(int, int)
422    */
423   public ITypedRegion[] computePartitioning(int offset, int length) {
424     List list = new ArrayList();
425
426     int end = offset + length;
427
428     int index = computeFlatNodeIndex(offset);
429     while (true) {
430       FlatNode prev = (index > 0) ? (FlatNode) nodes.get(index - 1) : null;
431
432       if (prev != null) {
433         if (prev.overlapsWith(offset, length)) {
434           addInnerPartitions(list, offset, length, prev);
435         }
436
437         if (end <= prev.offset + prev.length) {
438           break;
439         }
440       }
441
442       FlatNode next = (index < nodes.size()) ? (FlatNode) nodes.get(index) : null;
443
444       if (next == null || offset < next.offset) {
445         addOuterPartitions(list, offset, length, prev, next);
446       }
447
448       if (next == null) {
449         break;
450       }
451
452       ++index;
453     }
454
455     return (TypedRegion[]) list.toArray(new TypedRegion[list.size()]);
456   }
457
458   private void addOuterPartitions(List list, int offset, int length, FlatNode prev, FlatNode next) {
459     // limit region
460     int start = offset;
461     int end = offset + length;
462
463     if (prev != null && start < prev.offset + prev.length) {
464       start = prev.offset + prev.length;
465     }
466
467     if (next != null && next.offset < end) {
468       end = next.offset;
469     }
470
471     if (start == end) {
472       return;
473     }
474
475     if (outerDocument == null) {
476       list.add(new TypedRegion(start, end - start, getContentType(null, IDocument.DEFAULT_CONTENT_TYPE)));
477       return;
478     }
479
480     try {
481       // convert to outer offsets
482       start = outerDocument.getLocalOffset(start);
483       end = outerDocument.getLocalOffset(end);
484       if (end - start >= 0) {//jsurfer insert line
485         ITypedRegion[] regions = outerDocument.computePartitioning(start, end - start);
486
487         for (int i = 0; i < regions.length; i++) {
488           ITypedRegion region = regions[i];
489
490           // convert back to parent offsets
491           start = outerDocument.getParentOffset(region.getOffset());
492           end = start + region.getLength();
493
494           if (prev != null) {
495             offset = prev.offset + prev.length;
496             if (start < offset) {
497               start = offset;
498             }
499           }
500
501           if (next != null) {
502             offset = next.offset;
503             if (offset < end) {
504               end = offset;
505             }
506           }
507
508           list.add(new TypedRegion(start, end - start, getContentType(null, region.getType())));
509         }
510       }
511     } catch (BadLocationException x) {
512     }
513   }
514
515   private void addInnerPartitions(List list, int offset, int length, FlatNode position) {
516     InnerDocumentView innerDocument = null;
517     if (position instanceof ViewNode) {
518       // TODO: revisit condition
519       innerDocument = ((ViewNode) position).view;
520     }
521
522     if (innerDocument == null) {
523       // simple partition
524       list.add(new TypedRegion(position.offset, position.length, getContentType(position.type, null)));
525       return;
526     }
527
528     // multiplexing to inner view
529     try {
530       // limit region
531       int start = Math.max(offset, position.offset);
532       int end = Math.min(offset + length, position.offset + position.length);
533
534       // convert to document offsets
535       length = end - start;
536       offset = innerDocument.getLocalOffset(start);
537
538       ITypedRegion[] regions = innerDocument.computePartitioning(offset, length);
539
540       for (int i = 0; i < regions.length; i++) {
541         ITypedRegion region = regions[i];
542
543         // convert back to parent offsets
544         offset = innerDocument.getParentOffset(region.getOffset());
545         length = region.getLength();
546
547         list.add(new TypedRegion(offset, length, getContentType(position.type, region.getType())));
548       }
549     } catch (BadLocationException x) {
550     }
551   }
552
553   /*
554    * @see org.eclipse.jface.text.IDocumentPartitioner#getPartition(int)
555    */
556   public ITypedRegion getPartition(int offset) {
557     if (nodes.size() == 0) {
558       return getOuterPartition(offset, null, null);
559     }
560
561     int index = computeFlatNodeIndex(offset);
562     if (index < nodes.size()) {
563       FlatNode next = (FlatNode) nodes.get(index);
564
565       if (offset == next.offset) {
566         return getInnerPartition(offset, next);
567       }
568
569       if (index == 0) {
570         return getOuterPartition(offset, null, next);
571       }
572
573       FlatNode prev = (FlatNode) nodes.get(index - 1);
574
575       if (prev.includes(offset)) {
576         return getInnerPartition(offset, prev);
577       }
578
579       return getOuterPartition(offset, prev, next);
580     }
581
582     FlatNode prev = (FlatNode) nodes.get(nodes.size() - 1);
583
584     if (prev.includes(offset)) {
585       return getInnerPartition(offset, prev);
586     }
587
588     return getOuterPartition(offset, prev, null);
589   }
590
591   protected ITypedRegion getOuterPartition(int offset, FlatNode prev, FlatNode next) {
592     try {
593       int start, end;
594       String type;
595
596       if (outerDocument == null) {
597         start = 0;
598         end = document.getLength();
599         type = getContentType(null, IDocument.DEFAULT_CONTENT_TYPE);
600       } else {
601         ITypedRegion region = outerDocument.getPartition(outerDocument.getLocalOffset(offset));
602
603         start = region.getOffset();
604         end = start + region.getLength();
605
606         // convert to parent offset
607         start = outerDocument.getParentOffset(start);
608         end = outerDocument.getParentOffset(end);
609
610         type = getContentType(null, region.getType());
611       }
612
613       if (prev != null) {
614         offset = prev.offset + prev.length;
615         if (start < offset) {
616           start = offset;
617         }
618       }
619
620       if (next != null) {
621         offset = next.offset;
622         if (offset < end) {
623           end = offset;
624         }
625       }
626
627       return new TypedRegion(start, end - start, type);
628     } catch (BadLocationException x) {
629       throw new IllegalArgumentException();
630     }
631   }
632
633   protected ITypedRegion getInnerPartition(int offset, FlatNode position) {
634     if (position instanceof ViewNode) {
635       // TODO: revisit condition
636       InnerDocumentView innerDocument = ((ViewNode) position).view;
637
638       if (innerDocument != null) {
639         // multiplexing to inner view
640         try {
641           // convert to inner offset
642           ITypedRegion region = innerDocument.getPartition(innerDocument.getLocalOffset(offset));
643
644           // convert to parent offset
645           offset = innerDocument.getParentOffset(region.getOffset());
646
647           return new TypedRegion(offset, region.getLength(), getContentType(position.type, region.getType()));
648         } catch (BadLocationException x) {
649         }
650       }
651     }
652
653     // simple partition
654     return new TypedRegion(position.offset, position.length, position.type);
655   }
656 }