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