misc
[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.5 2004-11-10 20:39: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
440     int end = offset + length;
441
442     int index = computeFlatNodeIndex(offset);
443     while (true) {
444       FlatNode prev = (index > 0) ? (FlatNode) nodes.get(index - 1) : null;
445
446       if (prev != null) {
447         if (prev.overlapsWith(offset, length)) {
448           addInnerPartitions(list, offset, length, prev);
449         }
450
451         if (end <= prev.offset + prev.length) {
452           break;
453         }
454       }
455
456       FlatNode next = (index < nodes.size()) ? (FlatNode) nodes.get(index) : null;
457
458       if (next == null || offset < next.offset) {
459         addOuterPartitions(list, offset, length, prev, next);
460       }
461
462       if (next == null) {
463         break;
464       }
465
466       ++index;
467     }
468
469     return (TypedRegion[]) list.toArray(new TypedRegion[list.size()]);
470   }
471
472   private void addOuterPartitions(List list, int offset, int length, FlatNode prev, FlatNode next) {
473     // limit region
474     int start = offset;
475     int end = offset + length;
476
477     if (prev != null && start < prev.offset + prev.length) {
478       start = prev.offset + prev.length;
479     }
480
481     if (next != null && next.offset < end) {
482       end = next.offset;
483     }
484
485     if (start == end) {
486       return;
487     }
488
489     if (outerDocument == null) {
490       list.add(new TypedRegion(start, end - start, getContentType(null, IDocument.DEFAULT_CONTENT_TYPE)));
491       return;
492     }
493
494     try {
495       // convert to outer offsets
496       start = outerDocument.getLocalOffset(start);
497       end = outerDocument.getLocalOffset(end);
498       if (end - start >= 0) {//jsurfer insert line
499         ITypedRegion[] regions = outerDocument.computePartitioning(start, end - start);
500
501         for (int i = 0; i < regions.length; i++) {
502           ITypedRegion region = regions[i];
503
504           // convert back to parent offsets
505           start = outerDocument.getParentOffset(region.getOffset());
506           end = start + region.getLength();
507
508           if (prev != null) {
509             offset = prev.offset + prev.length;
510             if (start < offset) {
511               start = offset;
512             }
513           }
514
515           if (next != null) {
516             offset = next.offset;
517             if (offset < end) {
518               end = offset;
519             }
520           }
521
522           list.add(new TypedRegion(start, end - start, getContentType(null, region.getType())));
523         }
524       }
525     } catch (BadLocationException x) {
526     }
527   }
528
529   private void addInnerPartitions(List list, int offset, int length, FlatNode position) {
530     InnerDocumentView innerDocument = null;
531     if (position instanceof ViewNode) {
532       // TODO: revisit condition
533       innerDocument = ((ViewNode) position).view;
534     }
535
536     if (innerDocument == null) {
537       // simple partition
538       list.add(new TypedRegion(position.offset, position.length, getContentType(position.type, null)));
539       return;
540     }
541
542     // multiplexing to inner view
543     try {
544       // limit region
545       int start = Math.max(offset, position.offset);
546       int end = Math.min(offset + length, position.offset + position.length);
547
548       // convert to document offsets
549       length = end - start;
550       offset = innerDocument.getLocalOffset(start);
551
552       ITypedRegion[] regions = innerDocument.computePartitioning(offset, length);
553
554       for (int i = 0; i < regions.length; i++) {
555         ITypedRegion region = regions[i];
556
557         // convert back to parent offsets
558         offset = innerDocument.getParentOffset(region.getOffset());
559         length = region.getLength();
560
561         list.add(new TypedRegion(offset, length, getContentType(position.type, region.getType())));
562       }
563     } catch (BadLocationException x) {
564     }
565   }
566
567   /*
568    * @see org.eclipse.jface.text.IDocumentPartitioner#getPartition(int)
569    */
570   public ITypedRegion getPartition(int offset) {
571     if (nodes.size() == 0) {
572       return getOuterPartition(offset, null, null);
573     }
574
575     int index = computeFlatNodeIndex(offset);
576     if (index < nodes.size()) {
577       FlatNode next = (FlatNode) nodes.get(index);
578
579       if (offset == next.offset) {
580         return getInnerPartition(offset, next);
581       }
582
583       if (index == 0) {
584         return getOuterPartition(offset, null, next);
585       }
586
587       FlatNode prev = (FlatNode) nodes.get(index - 1);
588
589       if (prev.includes(offset)) {
590         return getInnerPartition(offset, prev);
591       }
592
593       return getOuterPartition(offset, prev, next);
594     }
595
596     FlatNode prev = (FlatNode) nodes.get(nodes.size() - 1);
597
598     if (prev.includes(offset)) {
599       return getInnerPartition(offset, prev);
600     }
601
602     return getOuterPartition(offset, prev, null);
603   }
604
605   protected ITypedRegion getOuterPartition(int offset, FlatNode prev, FlatNode next) {
606     try {
607       int start, end;
608       String type;
609
610       if (outerDocument == null) {
611         start = 0;
612         end = document.getLength();
613         type = getContentType(null, IDocument.DEFAULT_CONTENT_TYPE);
614       } else {
615         int outerOffset = outerDocument.getLocalOffset(offset);
616         //axelcl start
617         if (outerOffset < 0) {
618           start = 0;
619           end = document.getLength();
620           type = getContentType(null, IDocument.DEFAULT_CONTENT_TYPE);
621         } else {
622 //        axelcl end
623           ITypedRegion region = outerDocument.getPartition(outerOffset);
624
625           start = region.getOffset();
626           end = start + region.getLength();
627
628           // convert to parent offset
629           start = outerDocument.getParentOffset(start);
630           end = outerDocument.getParentOffset(end);
631
632           type = getContentType(null, region.getType());
633         }
634       }
635
636       if (prev != null) {
637         offset = prev.offset + prev.length;
638         if (start < offset) {
639           start = offset;
640         }
641       }
642
643       if (next != null) {
644         offset = next.offset;
645         if (offset < end) {
646           end = offset;
647         }
648       }
649
650       return new TypedRegion(start, end - start, type);
651     } catch (BadLocationException x) {
652       throw new IllegalArgumentException();
653     }
654   }
655
656   protected ITypedRegion getInnerPartition(int offset, FlatNode position) {
657     if (position instanceof ViewNode) {
658       // TODO: revisit condition
659       InnerDocumentView innerDocument = ((ViewNode) position).view;
660
661       if (innerDocument != null) {
662         // multiplexing to inner view
663         try {
664           // convert to inner offset
665           ITypedRegion region = innerDocument.getPartition(innerDocument.getLocalOffset(offset));
666
667           // convert to parent offset
668           offset = innerDocument.getParentOffset(region.getOffset());
669
670           return new TypedRegion(offset, region.getLength(), getContentType(position.type, region.getType()));
671         } catch (BadLocationException x) {
672         }
673       }
674     }
675
676     // simple partition
677     return new TypedRegion(position.offset, position.length, position.type);
678   }
679 }