Mercurial > projects > dwt-addons
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 } |