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