comparison dwtx/jface/text/projection/ProjectionDocument.d @ 129:eb30df5ca28b

Added JFace Text sources
author Frank Benoit <benoit@tionex.de>
date Sat, 23 Aug 2008 19:10:48 +0200
parents
children c4fb132a086c
comparison
equal deleted inserted replaced
128:8df1d4193877 129:eb30df5ca28b
1 /*******************************************************************************
2 * Copyright (c) 2000, 2008 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 * IBM Corporation - initial API and implementation
10 * Port to the D programming language:
11 * Frank Benoit <benoit@tionex.de>
12 *******************************************************************************/
13 module dwtx.jface.text.projection.ProjectionDocument;
14
15 import dwt.dwthelper.utils;
16
17 import java.util.ArrayList;
18 import java.util.List;
19
20 import dwtx.jface.text.AbstractDocument;
21 import dwtx.jface.text.BadLocationException;
22 import dwtx.jface.text.BadPositionCategoryException;
23 import dwtx.jface.text.DefaultLineTracker;
24 import dwtx.jface.text.DocumentEvent;
25 import dwtx.jface.text.IDocument;
26 import dwtx.jface.text.IDocumentExtension;
27 import dwtx.jface.text.IDocumentInformationMapping;
28 import dwtx.jface.text.IDocumentListener;
29 import dwtx.jface.text.ILineTracker;
30 import dwtx.jface.text.IRegion;
31 import dwtx.jface.text.ITextStore;
32 import dwtx.jface.text.Position;
33 import dwtx.jface.text.Region;
34 import dwtx.jface.text.TextUtilities;
35
36
37 /**
38 * A <code>ProjectionDocument</code> represents a projection of its master
39 * document. The contents of a projection document is a sequence of fragments of
40 * the master document, i.e. the projection document can be thought as being
41 * constructed from the master document by not copying the whole master document
42 * but omitting several ranges of the master document.
43 * <p>
44 * The projection document indirectly utilizes its master document as
45 * <code>ITextStore</code> by means of a <code>ProjectionTextStore</code>.
46 * <p>
47 * The content of a projection document can be changed in two ways. Either by a
48 * text replace applied to the master document or the projection document. Or by
49 * changing the projection between the master document and the projection
50 * document. For the latter the two methods <code>addMasterDocumentRange</code>
51 * and <code>removeMasterDocumentRange</code> are provided. For any
52 * manipulation, the projection document sends out a
53 * {@link dwtx.jface.text.projection.ProjectionDocumentEvent} describing
54 * the change.
55 * <p>
56 * Clients are not supposed to directly instantiate this class. In order to
57 * obtain a projection document, a
58 * {@link dwtx.jface.text.projection.ProjectionDocumentManager}should be
59 * used. This class is not intended to be subclassed outside of its origin
60 * package.</p>
61 *
62 * @since 3.0
63 * @noinstantiate This class is not intended to be instantiated by clients.
64 * @noextend This class is not intended to be subclassed by clients.
65 */
66 public class ProjectionDocument : AbstractDocument {
67
68
69 /**
70 * Prefix of the name of the position category used to keep track of the master
71 * document's fragments that correspond to the segments of the projection
72 * document.
73 */
74 private final static String FRAGMENTS_CATEGORY_PREFIX= "__fragmentsCategory"; //$NON-NLS-1$
75
76 /**
77 * Name of the position category used to keep track of the project
78 * document's segments that correspond to the fragments of the master
79 * document.
80 */
81 private final static String SEGMENTS_CATEGORY= "__segmentsCategory"; //$NON-NLS-1$
82
83
84 /** The master document */
85 private IDocument fMasterDocument;
86 /** The master document as document extension */
87 private IDocumentExtension fMasterDocumentExtension;
88 /** The fragments' position category */
89 private String fFragmentsCategory;
90 /** The segment's position category */
91 private String fSegmentsCategory;
92 /** The document event issued by the master document */
93 private DocumentEvent fMasterEvent;
94 /** The document event to be issued by the projection document */
95 private ProjectionDocumentEvent fSlaveEvent;
96 /** The original document event generated by a direct manipulation of this projection document */
97 private DocumentEvent fOriginalEvent;
98 /** Indicates whether the projection document initiated a master document update or not */
99 private bool fIsUpdating= false;
100 /** Indicated whether the projection document is in auto expand mode nor not */
101 private bool fIsAutoExpanding= false;
102 /** The position updater for the segments */
103 private SegmentUpdater fSegmentUpdater;
104 /** The position updater for the fragments */
105 private FragmentUpdater fFragmentsUpdater;
106 /** The projection mapping */
107 private ProjectionMapping fMapping;
108
109 /**
110 * Creates a projection document for the given master document.
111 *
112 * @param masterDocument the master document
113 */
114 public ProjectionDocument(IDocument masterDocument) {
115 super();
116
117 fMasterDocument= masterDocument;
118 if (fMasterDocument instanceof IDocumentExtension)
119 fMasterDocumentExtension= (IDocumentExtension) fMasterDocument;
120
121 fSegmentsCategory= SEGMENTS_CATEGORY;
122 fFragmentsCategory= FRAGMENTS_CATEGORY_PREFIX + hashCode();
123 fMasterDocument.addPositionCategory(fFragmentsCategory);
124 fFragmentsUpdater= new FragmentUpdater(fFragmentsCategory);
125 fMasterDocument.addPositionUpdater(fFragmentsUpdater);
126
127 fMapping= new ProjectionMapping(masterDocument, fFragmentsCategory, this, fSegmentsCategory);
128
129 ITextStore s= new ProjectionTextStore(masterDocument, fMapping);
130 ILineTracker tracker= new DefaultLineTracker();
131
132 setTextStore(s);
133 setLineTracker(tracker);
134
135 completeInitialization();
136
137 initializeProjection();
138 tracker.set(s.get(0, s.getLength()));
139 }
140
141 /**
142 * Disposes this projection document.
143 */
144 public void dispose() {
145 fMasterDocument.removePositionUpdater(fFragmentsUpdater);
146 try {
147 fMasterDocument.removePositionCategory(fFragmentsCategory);
148 } catch (BadPositionCategoryException x) {
149 // allow multiple dispose calls
150 }
151 }
152
153 private void internalError() {
154 throw new IllegalStateException();
155 }
156
157 /**
158 * Returns the fragments of the master documents.
159 *
160 * @return the fragment of the master document
161 */
162 protected final Position[] getFragments() {
163 try {
164 return fMasterDocument.getPositions(fFragmentsCategory);
165 } catch (BadPositionCategoryException e) {
166 internalError();
167 }
168 // unreachable
169 return null;
170 }
171
172 /**
173 * Returns the segments of this projection document.
174 *
175 * @return the segments of this projection document
176 */
177 protected final Position[] getSegments() {
178 try {
179 return getPositions(fSegmentsCategory);
180 } catch (BadPositionCategoryException e) {
181 internalError();
182 }
183 // unreachable
184 return null;
185 }
186
187 /**
188 * Returns the projection mapping used by this document.
189 *
190 * @return the projection mapping used by this document
191 * @deprecated As of 3.4, replaced by {@link #getDocumentInformationMapping()}
192 */
193 public ProjectionMapping getProjectionMapping(){
194 return fMapping;
195 }
196
197 /**
198 * Returns the projection mapping used by this document.
199 *
200 * @return the projection mapping used by this document
201 * @since 3.4
202 */
203 public IDocumentInformationMapping getDocumentInformationMapping() {
204 return fMapping;
205 }
206
207 /**
208 * Returns the master document of this projection document.
209 *
210 * @return the master document of this projection document
211 */
212 public IDocument getMasterDocument() {
213 return fMasterDocument;
214 }
215
216 /*
217 * @see dwtx.jface.text.IDocumentExtension4#getDefaultLineDelimiter()
218 * @since 3.1
219 */
220 public String getDefaultLineDelimiter() {
221 return TextUtilities.getDefaultLineDelimiter(fMasterDocument);
222 }
223
224 /**
225 * Initializes the projection document from the master document based on
226 * the master's fragments.
227 */
228 private void initializeProjection() {
229
230 try {
231
232 addPositionCategory(fSegmentsCategory);
233 fSegmentUpdater= new SegmentUpdater(fSegmentsCategory);
234 addPositionUpdater(fSegmentUpdater);
235
236 int offset= 0;
237 Position[] fragments= getFragments();
238 for (int i= 0; i < fragments.length; i++) {
239 Fragment fragment= (Fragment) fragments[i];
240 Segment segment= new Segment(offset, fragment.getLength());
241 segment.fragment= fragment;
242 addPosition(fSegmentsCategory, segment);
243 offset += fragment.length;
244 }
245
246 } catch (BadPositionCategoryException x) {
247 internalError();
248 } catch (BadLocationException x) {
249 internalError();
250 }
251 }
252
253 /**
254 * Creates a segment for the given fragment at the given position inside the list of segments.
255 *
256 * @param fragment the corresponding fragment
257 * @param index the index in the list of segments
258 * @return the created segment
259 * @throws BadLocationException in case the fragment is invalid
260 * @throws BadPositionCategoryException in case the segment category is invalid
261 */
262 private Segment createSegmentFor(Fragment fragment, int index) throws BadLocationException, BadPositionCategoryException {
263
264 int offset= 0;
265 if (index > 0) {
266 Position[] segments= getSegments();
267 Segment segment= (Segment) segments[index - 1];
268 offset= segment.getOffset() + segment.getLength();
269 }
270
271 Segment segment= new Segment(offset, 0);
272 segment.fragment= fragment;
273 fragment.segment= segment;
274 addPosition(fSegmentsCategory, segment);
275 return segment;
276 }
277
278 /**
279 * Adds the given range of the master document to this projection document.
280 *
281 * @param offsetInMaster offset of the master document range
282 * @param lengthInMaster length of the master document range
283 * @param masterDocumentEvent the master document event that causes this
284 * projection change or <code>null</code> if none
285 * @throws BadLocationException if the given range is invalid in the master
286 * document
287 */
288 private void internalAddMasterDocumentRange(int offsetInMaster, int lengthInMaster, DocumentEvent masterDocumentEvent) throws BadLocationException {
289 if (lengthInMaster is 0)
290 return;
291
292 try {
293
294 Position[] fragments= getFragments();
295 int index= fMasterDocument.computeIndexInCategory(fFragmentsCategory, offsetInMaster);
296
297 Fragment left= null;
298 Fragment right= null;
299
300 if (index < fragments.length) {
301 Fragment fragment= (Fragment) fragments[index];
302 if (offsetInMaster is fragment.offset)
303 if (fragment.length is 0) // the fragment does not overlap - it is a zero-length fragment at the same offset
304 left= fragment;
305 else
306 throw new IllegalArgumentException("overlaps with existing fragment"); //$NON-NLS-1$
307 if (offsetInMaster + lengthInMaster is fragment.offset)
308 right= fragment;
309 }
310
311 if (0 < index && index <= fragments.length) {
312 Fragment fragment= (Fragment) fragments[index - 1];
313 if (fragment.includes(offsetInMaster))
314 throw new IllegalArgumentException("overlaps with existing fragment"); //$NON-NLS-1$
315 if (fragment.getOffset() + fragment.getLength() is offsetInMaster)
316 left= fragment;
317 }
318
319 int offsetInSlave= 0;
320 if (index > 0) {
321 Fragment fragment= (Fragment) fragments[index - 1];
322 Segment segment= fragment.segment;
323 offsetInSlave= segment.getOffset() + segment.getLength();
324 }
325
326 ProjectionDocumentEvent event= new ProjectionDocumentEvent(this, offsetInSlave, 0, fMasterDocument.get(offsetInMaster, lengthInMaster), offsetInMaster, lengthInMaster, masterDocumentEvent);
327 super.fireDocumentAboutToBeChanged(event);
328
329 // check for neighboring fragment
330 if (left !is null && right !is null) {
331
332 int endOffset= right.getOffset() + right.getLength();
333 left.setLength(endOffset - left.getOffset());
334 left.segment.setLength(left.segment.getLength() + right.segment.getLength());
335
336 removePosition(fSegmentsCategory, right.segment);
337 fMasterDocument.removePosition(fFragmentsCategory, right);
338
339 } else if (left !is null) {
340 int endOffset= offsetInMaster +lengthInMaster;
341 left.setLength(endOffset - left.getOffset());
342 left.segment.markForStretch();
343
344 } else if (right !is null) {
345 right.setOffset(right.getOffset() - lengthInMaster);
346 right.setLength(right.getLength() + lengthInMaster);
347 right.segment.markForStretch();
348
349 } else {
350 // create a new segment
351 Fragment fragment= new Fragment(offsetInMaster, lengthInMaster);
352 fMasterDocument.addPosition(fFragmentsCategory, fragment);
353 Segment segment= createSegmentFor(fragment, index);
354 segment.markForStretch();
355 }
356
357 getTracker().replace(event.getOffset(), event.getLength(), event.getText());
358 super.fireDocumentChanged(event);
359
360 } catch (BadPositionCategoryException x) {
361 internalError();
362 }
363 }
364
365 /**
366 * Finds the fragment of the master document that represents the given range.
367 *
368 * @param offsetInMaster the offset of the range in the master document
369 * @param lengthInMaster the length of the range in the master document
370 * @return the fragment representing the given master document range
371 */
372 private Fragment findFragment(int offsetInMaster, int lengthInMaster) {
373 Position[] fragments= getFragments();
374 for (int i= 0; i < fragments.length; i++) {
375 Fragment f= (Fragment) fragments[i];
376 if (f.getOffset() <= offsetInMaster && offsetInMaster + lengthInMaster <= f.getOffset() + f.getLength())
377 return f;
378 }
379 return null;
380 }
381
382 /**
383 * Removes the given range of the master document from this projection
384 * document.
385 *
386 * @param offsetInMaster the offset of the range in the master document
387 * @param lengthInMaster the length of the range in the master document
388 *
389 * @throws BadLocationException if the given range is not valid in the
390 * master document
391 * @throws IllegalArgumentException if the given range is not projected in
392 * this projection document or is not completely comprised by
393 * an existing fragment
394 */
395 private void internalRemoveMasterDocumentRange(int offsetInMaster, int lengthInMaster) throws BadLocationException {
396 try {
397
398 IRegion imageRegion= fMapping.toExactImageRegion(new Region(offsetInMaster, lengthInMaster));
399 if (imageRegion is null)
400 throw new IllegalArgumentException();
401
402 Fragment fragment= findFragment(offsetInMaster, lengthInMaster);
403 if (fragment is null)
404 throw new IllegalArgumentException();
405
406 ProjectionDocumentEvent event= new ProjectionDocumentEvent(this, imageRegion.getOffset(), imageRegion.getLength(), "", offsetInMaster, lengthInMaster); //$NON-NLS-1$
407 super.fireDocumentAboutToBeChanged(event);
408
409 if (fragment.getOffset() is offsetInMaster) {
410 fragment.setOffset(offsetInMaster + lengthInMaster);
411 fragment.setLength(fragment.getLength() - lengthInMaster);
412 } else if (fragment.getOffset() + fragment.getLength() is offsetInMaster + lengthInMaster) {
413 fragment.setLength(fragment.getLength() - lengthInMaster);
414 } else {
415 // split fragment into three fragments, let position updater remove it
416
417 // add fragment for the region to be removed
418 Fragment newFragment= new Fragment(offsetInMaster, lengthInMaster);
419 Segment segment= new Segment(imageRegion.getOffset(), imageRegion.getLength());
420 newFragment.segment= segment;
421 segment.fragment= newFragment;
422 fMasterDocument.addPosition(fFragmentsCategory, newFragment);
423 addPosition(fSegmentsCategory, segment);
424
425 // add fragment for the remainder right of the deleted range in the original fragment
426 int offset= offsetInMaster + lengthInMaster;
427 newFragment= new Fragment(offset, fragment.getOffset() + fragment.getLength() - offset);
428 offset= imageRegion.getOffset() + imageRegion.getLength();
429 segment= new Segment(offset, fragment.segment.getOffset() + fragment.segment.getLength() - offset);
430 newFragment.segment= segment;
431 segment.fragment= newFragment;
432 fMasterDocument.addPosition(fFragmentsCategory, newFragment);
433 addPosition(fSegmentsCategory, segment);
434
435 // adjust length of initial fragment (the left one)
436 fragment.setLength(offsetInMaster - fragment.getOffset());
437 fragment.segment.setLength(imageRegion.getOffset() - fragment.segment.getOffset());
438 }
439
440 getTracker().replace(event.getOffset(), event.getLength(), event.getText());
441 super.fireDocumentChanged(event);
442
443 } catch (BadPositionCategoryException x) {
444 internalError();
445 }
446 }
447
448 /**
449 * Returns the sequence of all master document regions which are contained
450 * in the given master document range and which are not yet part of this
451 * projection document.
452 *
453 * @param offsetInMaster the range offset in the master document
454 * @param lengthInMaster the range length in the master document
455 * @return the sequence of regions which are not yet part of the projection
456 * document
457 * @throws BadLocationException in case the given range is invalid in the
458 * master document
459 */
460 public final IRegion[] computeUnprojectedMasterRegions(int offsetInMaster, int lengthInMaster) throws BadLocationException {
461
462 IRegion[] fragments= null;
463 IRegion imageRegion= fMapping.toImageRegion(new Region(offsetInMaster, lengthInMaster));
464 if (imageRegion !is null)
465 fragments= fMapping.toExactOriginRegions(imageRegion);
466
467 if (fragments is null || fragments.length is 0)
468 return new IRegion[] { new Region(offsetInMaster, lengthInMaster) };
469
470 List gaps= new ArrayList();
471
472 IRegion region= fragments[0];
473 if (offsetInMaster < region.getOffset())
474 gaps.add(new Region(offsetInMaster, region.getOffset() - offsetInMaster));
475
476 for (int i= 0; i < fragments.length - 1; i++) {
477 IRegion left= fragments[i];
478 IRegion right= fragments[i + 1];
479 int leftEnd= left.getOffset() + left.getLength();
480 if (leftEnd < right.getOffset())
481 gaps.add(new Region(leftEnd, right.getOffset() - leftEnd));
482 }
483
484 region= fragments[fragments.length - 1];
485 int leftEnd= region.getOffset() + region.getLength();
486 int rightEnd= offsetInMaster + lengthInMaster;
487 if (leftEnd < rightEnd)
488 gaps.add(new Region(leftEnd, rightEnd - leftEnd));
489
490 IRegion[] result= new IRegion[gaps.size()];
491 gaps.toArray(result);
492 return result;
493 }
494
495 /**
496 * Returns the first master document region which is contained in the given
497 * master document range and which is not yet part of this projection
498 * document.
499 *
500 * @param offsetInMaster the range offset in the master document
501 * @param lengthInMaster the range length in the master document
502 * @return the first region that is not yet part of the projection document
503 * @throws BadLocationException in case the given range is invalid in the
504 * master document
505 * @since 3.1
506 */
507 private IRegion computeFirstUnprojectedMasterRegion(int offsetInMaster, int lengthInMaster) throws BadLocationException {
508
509 IRegion[] fragments= null;
510 IRegion imageRegion= fMapping.toImageRegion(new Region(offsetInMaster, lengthInMaster));
511 if (imageRegion !is null)
512 fragments= fMapping.toExactOriginRegions(imageRegion);
513
514 if (fragments is null || fragments.length is 0)
515 return new Region(offsetInMaster, lengthInMaster);
516
517 IRegion region= fragments[0];
518 if (offsetInMaster < region.getOffset())
519 return new Region(offsetInMaster, region.getOffset() - offsetInMaster);
520
521 for (int i= 0; i < fragments.length - 1; i++) {
522 IRegion left= fragments[i];
523 IRegion right= fragments[i + 1];
524 int leftEnd= left.getOffset() + left.getLength();
525 if (leftEnd < right.getOffset())
526 return new Region(leftEnd, right.getOffset() - leftEnd);
527 }
528
529 region= fragments[fragments.length - 1];
530 int leftEnd= region.getOffset() + region.getLength();
531 int rightEnd= offsetInMaster + lengthInMaster;
532 if (leftEnd < rightEnd)
533 return new Region(leftEnd, rightEnd - leftEnd);
534
535 return null;
536 }
537
538 /**
539 * Ensures that the given range of the master document is part of this
540 * projection document.
541 *
542 * @param offsetInMaster the offset of the master document range
543 * @param lengthInMaster the length of the master document range
544 * @throws BadLocationException in case the master event is not valid
545 */
546 public void addMasterDocumentRange(int offsetInMaster, int lengthInMaster) throws BadLocationException {
547 addMasterDocumentRange(offsetInMaster, lengthInMaster, null);
548 }
549
550 /**
551 * Ensures that the given range of the master document is part of this
552 * projection document.
553 *
554 * @param offsetInMaster the offset of the master document range
555 * @param lengthInMaster the length of the master document range
556 * @param masterDocumentEvent the master document event which causes this
557 * projection change, or <code>null</code> if none
558 * @throws BadLocationException in case the master event is not valid
559 */
560 private void addMasterDocumentRange(int offsetInMaster, int lengthInMaster, DocumentEvent masterDocumentEvent) throws BadLocationException {
561 /*
562 * Calling internalAddMasterDocumentRange may cause other master ranges
563 * to become unfolded, resulting in re-entrant calls to this method. In
564 * order to not add a region twice, we have to compute the next region
565 * to add in every iteration.
566 *
567 * To place an upper bound on the number of iterations, we use the number
568 * of fragments * 2 as the limit.
569 */
570 int limit= Math.max(getFragments().length * 2, 20);
571 while (true) {
572 if (limit-- < 0)
573 throw new IllegalArgumentException("safety loop termination"); //$NON-NLS-1$
574
575 IRegion gap= computeFirstUnprojectedMasterRegion(offsetInMaster, lengthInMaster);
576 if (gap is null)
577 return;
578
579 internalAddMasterDocumentRange(gap.getOffset(), gap.getLength(), masterDocumentEvent);
580 }
581 }
582
583 /**
584 * Ensures that the given range of the master document is not part of this
585 * projection document.
586 *
587 * @param offsetInMaster the offset of the master document range
588 * @param lengthInMaster the length of the master document range
589 * @throws BadLocationException in case the master event is not valid
590 */
591 public void removeMasterDocumentRange(int offsetInMaster, int lengthInMaster) throws BadLocationException {
592 IRegion[] fragments= computeProjectedMasterRegions(offsetInMaster, lengthInMaster);
593 if (fragments is null || fragments.length is 0)
594 return;
595
596 for (int i= 0; i < fragments.length; i++) {
597 IRegion fragment= fragments[i];
598 internalRemoveMasterDocumentRange(fragment.getOffset(), fragment.getLength());
599 }
600 }
601
602 /**
603 * Returns the sequence of all master document regions with are contained in the given master document
604 * range and which are part of this projection document. May return <code>null</code> if no such
605 * regions exist.
606 *
607 * @param offsetInMaster the range offset in the master document
608 * @param lengthInMaster the range length in the master document
609 * @return the sequence of regions which are part of the projection document or <code>null</code>
610 * @throws BadLocationException in case the given range is invalid in the master document
611 */
612 public final IRegion[] computeProjectedMasterRegions(int offsetInMaster, int lengthInMaster) throws BadLocationException {
613 IRegion imageRegion= fMapping.toImageRegion(new Region(offsetInMaster, lengthInMaster));
614 return imageRegion !is null ? fMapping.toExactOriginRegions(imageRegion) : null;
615 }
616
617 /**
618 * Returns whether this projection is being updated.
619 *
620 * @return <code>true</code> if the document is updating
621 */
622 protected bool isUpdating() {
623 return fIsUpdating;
624 }
625
626 /*
627 * @see dwtx.jface.text.IDocument#replace(int, int, java.lang.String)
628 */
629 public void replace(int offset, int length, String text) throws BadLocationException {
630 try {
631 fIsUpdating= true;
632 if (fMasterDocumentExtension !is null)
633 fMasterDocumentExtension.stopPostNotificationProcessing();
634
635 super.replace(offset, length, text);
636
637 } finally {
638 fIsUpdating= false;
639 if (fMasterDocumentExtension !is null)
640 fMasterDocumentExtension.resumePostNotificationProcessing();
641 }
642 }
643
644 /*
645 * @see dwtx.jface.text.IDocument#set(java.lang.String)
646 */
647 public void set(String text) {
648 try {
649 fIsUpdating= true;
650 if (fMasterDocumentExtension !is null)
651 fMasterDocumentExtension.stopPostNotificationProcessing();
652
653 super.set(text);
654
655 } finally {
656 fIsUpdating= false;
657 if (fMasterDocumentExtension !is null)
658 fMasterDocumentExtension.resumePostNotificationProcessing();
659 }
660 }
661
662 /**
663 * Transforms a document event of the master document into a projection
664 * document based document event.
665 *
666 * @param masterEvent the master document event
667 * @return the slave document event
668 * @throws BadLocationException in case the master event is not valid
669 */
670 private ProjectionDocumentEvent normalize(DocumentEvent masterEvent) throws BadLocationException {
671 if (!isUpdating()) {
672 IRegion imageRegion= fMapping.toExactImageRegion(new Region(masterEvent.getOffset(), masterEvent.getLength()));
673 if (imageRegion !is null)
674 return new ProjectionDocumentEvent(this, imageRegion.getOffset(), imageRegion.getLength(), masterEvent.getText(), masterEvent);
675 return null;
676 }
677
678 ProjectionDocumentEvent event= new ProjectionDocumentEvent(this, fOriginalEvent.getOffset(), fOriginalEvent.getLength(), fOriginalEvent.getText(), masterEvent);
679 fOriginalEvent= null;
680 return event;
681 }
682
683 /**
684 * Ensures that when the master event affects this projection document, that the whole region described by the
685 * event is part of this projection document.
686 *
687 * @param masterEvent the master document event
688 * @return <code>true</code> if masterEvent affects this projection document
689 * @throws BadLocationException in case the master event is not valid
690 */
691 protected final bool adaptProjectionToMasterChange(DocumentEvent masterEvent) throws BadLocationException {
692 if (!isUpdating() && fFragmentsUpdater.affectsPositions(masterEvent) || fIsAutoExpanding && masterEvent.getLength() > 0) {
693
694 addMasterDocumentRange(masterEvent.getOffset(), masterEvent.getLength(), masterEvent);
695 return true;
696
697 } else if (fMapping.getImageLength() is 0 && masterEvent.getLength() is 0) {
698
699 Position[] fragments= getFragments();
700 if (fragments.length is 0) {
701 // there is no segment in this projection document, thus one must be created
702 // need to bypass the usual infrastructure as the new segment/fragment would be of length 0 and thus the segmentation be not well formed
703 try {
704 Fragment fragment= new Fragment(0, 0);
705 fMasterDocument.addPosition(fFragmentsCategory, fragment);
706 createSegmentFor(fragment, 0);
707 } catch (BadPositionCategoryException x) {
708 internalError();
709 }
710 }
711 }
712
713 return isUpdating();
714 }
715
716 /**
717 * When called, this projection document is informed about a forthcoming
718 * change of its master document. This projection document checks whether
719 * the master document change affects it and if so informs all document
720 * listeners.
721 *
722 * @param masterEvent the master document event
723 */
724 public void masterDocumentAboutToBeChanged(DocumentEvent masterEvent) {
725 try {
726
727 bool assertNotNull= adaptProjectionToMasterChange(masterEvent);
728 fSlaveEvent= normalize(masterEvent);
729 if (assertNotNull && fSlaveEvent is null)
730 internalError();
731
732 fMasterEvent= masterEvent;
733 if (fSlaveEvent !is null)
734 delayedFireDocumentAboutToBeChanged();
735
736 } catch (BadLocationException e) {
737 internalError();
738 }
739 }
740
741 /**
742 * When called, this projection document is informed about a change of its
743 * master document. If this projection document is affected it informs all
744 * of its document listeners.
745 *
746 * @param masterEvent the master document event
747 */
748 public void masterDocumentChanged(DocumentEvent masterEvent) {
749 if ( !isUpdating() && masterEvent is fMasterEvent) {
750 if (fSlaveEvent !is null) {
751 try {
752 getTracker().replace(fSlaveEvent.getOffset(), fSlaveEvent.getLength(), fSlaveEvent.getText());
753 fireDocumentChanged(fSlaveEvent);
754 } catch (BadLocationException e) {
755 internalError();
756 }
757 } else if (ensureWellFormedSegmentation(masterEvent.getOffset()))
758 fMapping.projectionChanged();
759 }
760 }
761
762 /*
763 * @see dwtx.jface.text.AbstractDocument#fireDocumentAboutToBeChanged(dwtx.jface.text.DocumentEvent)
764 */
765 protected void fireDocumentAboutToBeChanged(DocumentEvent event) {
766 fOriginalEvent= event;
767 // delay it until there is a notification from the master document
768 // at this point, it is expensive to construct the master document information
769 }
770
771 /**
772 * Fires the slave document event as about-to-be-changed event to all registered listeners.
773 */
774 private void delayedFireDocumentAboutToBeChanged() {
775 super.fireDocumentAboutToBeChanged(fSlaveEvent);
776 }
777
778 /**
779 * Ignores the given event and sends the semantically equal slave document event instead.
780 *
781 * @param event the event to be ignored
782 */
783 protected void fireDocumentChanged(DocumentEvent event) {
784 super.fireDocumentChanged(fSlaveEvent);
785 }
786
787 /*
788 * @see dwtx.jface.text.AbstractDocument#updateDocumentStructures(dwtx.jface.text.DocumentEvent)
789 */
790 protected void updateDocumentStructures(DocumentEvent event) {
791 super.updateDocumentStructures(event);
792 ensureWellFormedSegmentation(computeAnchor(event));
793 fMapping.projectionChanged();
794 }
795
796 private int computeAnchor(DocumentEvent event) {
797 if (event instanceof ProjectionDocumentEvent) {
798 ProjectionDocumentEvent slave= (ProjectionDocumentEvent) event;
799 Object changeType= slave.getChangeType();
800 if (ProjectionDocumentEvent.CONTENT_CHANGE is changeType) {
801 DocumentEvent master= slave.getMasterEvent();
802 if (master !is null)
803 return master.getOffset();
804 } else if (ProjectionDocumentEvent.PROJECTION_CHANGE is changeType) {
805 return slave.getMasterOffset();
806 }
807 }
808 return -1;
809 }
810
811 private bool ensureWellFormedSegmentation(int anchorOffset) {
812 bool changed= false;
813 Position[] segments= getSegments();
814 for (int i= 0; i < segments.length; i++) {
815 Segment segment= (Segment) segments[i];
816 if (segment.isDeleted() || segment.getLength() is 0) {
817 try {
818 removePosition(fSegmentsCategory, segment);
819 fMasterDocument.removePosition(fFragmentsCategory, segment.fragment);
820 changed= true;
821 } catch (BadPositionCategoryException e) {
822 internalError();
823 }
824 } else if (i < segments.length - 1) {
825 Segment next= (Segment) segments[i + 1];
826 if (next.isDeleted() || next.getLength() is 0)
827 continue;
828 Fragment fragment= segment.fragment;
829 if (fragment.getOffset() + fragment.getLength() is next.fragment.getOffset()) {
830 // join fragments and their corresponding segments
831 segment.setLength(segment.getLength() + next.getLength());
832 fragment.setLength(fragment.getLength() + next.fragment.getLength());
833 next.delete();
834 }
835 }
836 }
837
838 if (changed && anchorOffset !is -1) {
839 Position[] changedSegments= getSegments();
840 if (changedSegments is null || changedSegments.length is 0) {
841 Fragment fragment= new Fragment(anchorOffset, 0);
842 try {
843 fMasterDocument.addPosition(fFragmentsCategory, fragment);
844 createSegmentFor(fragment, 0);
845 } catch (BadLocationException e) {
846 internalError();
847 } catch (BadPositionCategoryException e) {
848 internalError();
849 }
850 }
851 }
852
853 return changed;
854 }
855
856 /*
857 * @see IDocumentExtension#registerPostNotificationReplace(IDocumentListener, IDocumentExtension.IReplace)
858 */
859 public void registerPostNotificationReplace(IDocumentListener owner, IDocumentExtension.IReplace replace) {
860 if (!isUpdating())
861 throw new UnsupportedOperationException();
862 super.registerPostNotificationReplace(owner, replace);
863 }
864
865 /**
866 * Sets the auto expand mode for this document.
867 *
868 * @param autoExpandMode <code>true</code> if auto-expanding
869 */
870 public void setAutoExpandMode(bool autoExpandMode) {
871 fIsAutoExpanding= autoExpandMode;
872 }
873
874 /**
875 * Replaces all master document ranges with the given master document range.
876 *
877 * @param offsetInMaster the offset in the master document
878 * @param lengthInMaster the length in the master document
879 * @throws BadLocationException if the given range of the master document is not valid
880 */
881 public void replaceMasterDocumentRanges(int offsetInMaster, int lengthInMaster) throws BadLocationException {
882 try {
883
884 ProjectionDocumentEvent event= new ProjectionDocumentEvent(this, 0, fMapping.getImageLength(), fMasterDocument.get(offsetInMaster, lengthInMaster), offsetInMaster, lengthInMaster);
885 super.fireDocumentAboutToBeChanged(event);
886
887 Position[] fragments= getFragments();
888 for (int i= 0; i < fragments.length; i++) {
889 Fragment fragment= (Fragment) fragments[i];
890 fMasterDocument.removePosition(fFragmentsCategory, fragment);
891 removePosition(fSegmentsCategory, fragment.segment);
892 }
893
894 Fragment fragment= new Fragment(offsetInMaster, lengthInMaster);
895 Segment segment= new Segment(0, 0);
896 segment.fragment= fragment;
897 fragment.segment= segment;
898 fMasterDocument.addPosition(fFragmentsCategory, fragment);
899 addPosition(fSegmentsCategory, segment);
900
901 getTracker().set(fMasterDocument.get(offsetInMaster, lengthInMaster));
902 super.fireDocumentChanged(event);
903
904 } catch (BadPositionCategoryException x) {
905 internalError();
906 }
907 }
908 }