1
1
/*******************************************************************************
2
- * Copyright (c) 2000, 2018 IBM Corporation and others.
2
+ * Copyright (c) 2000, 2025 IBM Corporation and others.
3
3
*
4
4
* This program and the accompanying materials
5
5
* are made available under the terms of the Eclipse Public License 2.0
33
33
import org .eclipse .swt .widgets .Display ;
34
34
35
35
import org .eclipse .core .runtime .Assert ;
36
+ import org .eclipse .core .runtime .ILog ;
37
+ import org .eclipse .core .runtime .IStatus ;
38
+ import org .eclipse .core .runtime .Status ;
36
39
37
40
import org .eclipse .jface .internal .text .SelectionProcessor ;
38
41
@@ -272,6 +275,32 @@ private void computeExpectedExecutionCosts() {
272
275
}
273
276
}
274
277
278
+ /**
279
+ * An {@link IDocumentListener} that makes sure that {@link #fVisibleRegionDuringProjection} is
280
+ * updated when the document changes and ensures that the collapsed region after the visible
281
+ * region is recreated appropriately.
282
+ */
283
+ private final class UpdateDocumentListener implements IDocumentListener {
284
+ @ Override
285
+ public void documentChanged (DocumentEvent event ) {
286
+ if (fVisibleRegionDuringProjection == null ) {
287
+ return ;
288
+ }
289
+ int oldLength = event .getLength ();
290
+ int newLength = event .getText ().length ();
291
+ int oldVisibleRegionEnd = fVisibleRegionDuringProjection .getOffset () + fVisibleRegionDuringProjection .getLength ();
292
+ if (event .getOffset () < fVisibleRegionDuringProjection .getOffset ()) {
293
+ fVisibleRegionDuringProjection = new Region (fVisibleRegionDuringProjection .getOffset () + newLength - oldLength , fVisibleRegionDuringProjection .getLength ());
294
+ } else if (event .getOffset () + oldLength <= oldVisibleRegionEnd ) {
295
+ fVisibleRegionDuringProjection = new Region (fVisibleRegionDuringProjection .getOffset (), fVisibleRegionDuringProjection .getLength () + newLength - oldLength );
296
+ }
297
+ }
298
+
299
+ @ Override
300
+ public void documentAboutToBeChanged (DocumentEvent event ) {
301
+ }
302
+ }
303
+
275
304
/** The projection annotation model used by this viewer. */
276
305
private ProjectionAnnotationModel fProjectionAnnotationModel ;
277
306
/** The annotation model listener */
@@ -292,6 +321,11 @@ private void computeExpectedExecutionCosts() {
292
321
private IDocument fReplaceVisibleDocumentExecutionTrigger ;
293
322
/** <code>true</code> if projection was on the last time we switched to segmented mode. */
294
323
private boolean fWasProjectionEnabled ;
324
+ /**
325
+ * The region set by {@link #setVisibleRegion(int, int)} during projection or <code>null</code>
326
+ * if not in a projection
327
+ */
328
+ private IRegion fVisibleRegionDuringProjection ;
295
329
/** The queue of projection commands used to assess the costs of projection changes. */
296
330
private ProjectionCommandQueue fCommandQueue ;
297
331
/**
@@ -301,6 +335,8 @@ private void computeExpectedExecutionCosts() {
301
335
*/
302
336
private int fDeletedLines ;
303
337
338
+ private UpdateDocumentListener fUpdateDocumentListener ;
339
+
304
340
305
341
/**
306
342
* Creates a new projection source viewer.
@@ -313,6 +349,7 @@ private void computeExpectedExecutionCosts() {
313
349
*/
314
350
public ProjectionViewer (Composite parent , IVerticalRuler ruler , IOverviewRuler overviewRuler , boolean showsAnnotationOverview , int styles ) {
315
351
super (parent , ruler , overviewRuler , showsAnnotationOverview , styles );
352
+ fUpdateDocumentListener = new UpdateDocumentListener ();
316
353
}
317
354
318
355
/**
@@ -510,6 +547,14 @@ public final void disableProjection() {
510
547
fProjectionAnnotationModel .removeAllAnnotations ();
511
548
fFindReplaceDocumentAdapter = null ;
512
549
fireProjectionDisabled ();
550
+ if (fVisibleRegionDuringProjection != null ) {
551
+ super .setVisibleRegion (fVisibleRegionDuringProjection .getOffset (), fVisibleRegionDuringProjection .getLength ());
552
+ fVisibleRegionDuringProjection = null ;
553
+ }
554
+ IDocument document = getDocument ();
555
+ if (document != null ) {
556
+ document .removeDocumentListener (fUpdateDocumentListener );
557
+ }
513
558
}
514
559
}
515
560
@@ -521,6 +566,15 @@ public final void enableProjection() {
521
566
addProjectionAnnotationModel (getVisualAnnotationModel ());
522
567
fFindReplaceDocumentAdapter = null ;
523
568
fireProjectionEnabled ();
569
+ IDocument document = getDocument ();
570
+ if (document == null ) {
571
+ return ;
572
+ }
573
+ IRegion visibleRegion = getVisibleRegion ();
574
+ if (visibleRegion != null && (visibleRegion .getOffset () != 0 || visibleRegion .getLength () != 0 ) && visibleRegion .getLength () < document .getLength ()) {
575
+ setVisibleRegion (visibleRegion .getOffset (), visibleRegion .getLength ());
576
+ }
577
+ document .addDocumentListener (fUpdateDocumentListener );
524
578
}
525
579
}
526
580
@@ -529,6 +583,10 @@ private void expandAll() {
529
583
IDocument doc = getDocument ();
530
584
int length = doc == null ? 0 : doc .getLength ();
531
585
if (isProjectionMode ()) {
586
+ if (fVisibleRegionDuringProjection != null ) {
587
+ offset = fVisibleRegionDuringProjection .getOffset ();
588
+ length = fVisibleRegionDuringProjection .getLength ();
589
+ }
532
590
fProjectionAnnotationModel .expandAll (offset , length );
533
591
}
534
592
}
@@ -683,9 +741,75 @@ private int toLineStart(IDocument document, int offset, boolean testLastLine) th
683
741
684
742
@ Override
685
743
public void setVisibleRegion (int start , int length ) {
686
- fWasProjectionEnabled = isProjectionMode ();
687
- disableProjection ();
688
- super .setVisibleRegion (start , length );
744
+ if (!isProjectionMode ()) {
745
+ super .setVisibleRegion (start , length );
746
+ return ;
747
+ }
748
+ IDocument document = getDocument ();
749
+ if (document == null ) {
750
+ return ;
751
+ }
752
+ try {
753
+ // If the visible region changes, make sure collapsed regions outside of the old visible regions are expanded
754
+ // and collapse everything outside the new visible region
755
+ int end = computeEndOfVisibleRegion (start , length , document );
756
+ expandOutsideCurrentVisibleRegion (document );
757
+ collapseOutsideOfNewVisibleRegion (start , end , document );
758
+ fVisibleRegionDuringProjection = new Region (start , end - start - 1 );
759
+ } catch (BadLocationException e ) {
760
+ ILog log = ILog .of (getClass ());
761
+ log .log (new Status (IStatus .WARNING , getClass (), IStatus .OK , null , e ));
762
+ }
763
+ }
764
+
765
+ private void expandOutsideCurrentVisibleRegion (IDocument document ) throws BadLocationException {
766
+ if (fVisibleRegionDuringProjection != null ) {
767
+ expand (0 , fVisibleRegionDuringProjection .getOffset (), false , true );
768
+ int oldEnd = fVisibleRegionDuringProjection .getOffset () + fVisibleRegionDuringProjection .getLength ();
769
+ int length = document .getLength () - oldEnd ;
770
+ if (length > 0 ) {
771
+ expand (oldEnd , length , false , true );
772
+ }
773
+ }
774
+ }
775
+
776
+ private void collapseOutsideOfNewVisibleRegion (int start , int end , IDocument document ) throws BadLocationException {
777
+ int documentLength = document .getLength ();
778
+ collapse (0 , start , true , true );
779
+
780
+ int endInvisibleRegionLength = documentLength - end ;
781
+
782
+ if (isLineBreak (document .getChar (documentLength - 1 ))) {
783
+ // if the file ends with an empty line, make sure it is included as well (ensuring the user doesn't accidentially remove parts outside the visible region)
784
+ endInvisibleRegionLength ++;
785
+ }
786
+ if (endInvisibleRegionLength > 0 ) {
787
+ collapse (end , endInvisibleRegionLength , true , true );
788
+ }
789
+ }
790
+
791
+ private static int computeEndOfVisibleRegion (int start , int length , IDocument document ) throws BadLocationException {
792
+ int documentLength = document .getLength ();
793
+ int end = start + length + 1 ;
794
+ // ensure that trailing whitespace is included
795
+ // In this case, the line break needs to be included as well
796
+ boolean visibleRegionEndsWithTrailingWhitespace = end < documentLength && isWhitespaceButNotNewline (document .getChar (end - 1 ));
797
+ while (end < documentLength && isWhitespaceButNotNewline (document .getChar (end ))) {
798
+ end ++;
799
+ visibleRegionEndsWithTrailingWhitespace = true ;
800
+ }
801
+ if (visibleRegionEndsWithTrailingWhitespace && end < documentLength && isLineBreak (document .getChar (end ))) {
802
+ end ++;
803
+ }
804
+ return end ;
805
+ }
806
+
807
+ private static boolean isWhitespaceButNotNewline (char c ) {
808
+ return Character .isWhitespace (c ) && !isLineBreak (c );
809
+ }
810
+
811
+ private static boolean isLineBreak (char c ) {
812
+ return c == '\n' || c == '\r' ;
689
813
}
690
814
691
815
@ Override
@@ -710,7 +834,9 @@ public void resetVisibleRegion() {
710
834
711
835
@ Override
712
836
public IRegion getVisibleRegion () {
713
- disableProjection ();
837
+ if (fVisibleRegionDuringProjection != null ) {
838
+ return fVisibleRegionDuringProjection ;
839
+ }
714
840
IRegion visibleRegion = getModelCoverage ();
715
841
if (visibleRegion == null )
716
842
visibleRegion = new Region (0 , 0 );
@@ -720,7 +846,9 @@ public IRegion getVisibleRegion() {
720
846
721
847
@ Override
722
848
public boolean overlapsWithVisibleRegion (int offset , int length ) {
723
- disableProjection ();
849
+ if (fVisibleRegionDuringProjection != null ) {
850
+ return TextUtilities .overlaps (fVisibleRegionDuringProjection , new Region (offset , length ));
851
+ }
724
852
IRegion coverage = getModelCoverage ();
725
853
if (coverage == null )
726
854
return false ;
@@ -769,10 +897,16 @@ private void executeReplaceVisibleDocument(IDocument visibleDocument) {
769
897
*
770
898
* @param offset the offset of the range to hide
771
899
* @param length the length of the range to hide
772
- * @param fireRedraw <code>true</code> if a redraw request should be issued, <code>false</code> otherwise
900
+ * @param fireRedraw <code>true</code> if a redraw request should be issued, <code>false</code>
901
+ * otherwise
902
+ * @param performOutsideVisibleRegion <code>true</code> if the range should be collapsed if it
903
+ * overlaps with anything outside of the visible region, <code>false</code> otherwise
773
904
* @throws BadLocationException in case the range is invalid
774
905
*/
775
- private void collapse (int offset , int length , boolean fireRedraw ) throws BadLocationException {
906
+ private void collapse (int offset , int length , boolean fireRedraw , boolean performOutsideVisibleRegion ) throws BadLocationException {
907
+ if (!performOutsideVisibleRegion && overlapsWithNonVisibleRegions (offset , length )) {
908
+ return ;
909
+ }
776
910
ProjectionDocument projection = null ;
777
911
778
912
IDocument visibleDocument = getVisibleDocument ();
@@ -802,17 +936,23 @@ private void collapse(int offset, int length, boolean fireRedraw) throws BadLoca
802
936
}
803
937
}
804
938
939
+
805
940
/**
806
941
* Makes the given range visible again while not changing the folding state of any contained
807
942
* ranges. If requested, a redraw request is issued.
808
943
*
809
944
* @param offset the offset of the range to be expanded
810
945
* @param length the length of the range to be expanded
811
- * @param fireRedraw <code>true</code> if a redraw request should be issued,
812
- * <code>false</code> otherwise
946
+ * @param fireRedraw <code>true</code> if a redraw request should be issued, <code>false</code>
947
+ * otherwise
948
+ * @param performOutsideVisibleRegion <code>true</code> if the range should be collapsed if it
949
+ * overlaps with anything outside of the visible region, <code>false</code> otherwise
813
950
* @throws BadLocationException in case the range is invalid
814
951
*/
815
- private void expand (int offset , int length , boolean fireRedraw ) throws BadLocationException {
952
+ private void expand (int offset , int length , boolean fireRedraw , boolean performOutsideVisibleRegion ) throws BadLocationException {
953
+ if (!performOutsideVisibleRegion && overlapsWithNonVisibleRegions (offset , length )) {
954
+ return ;
955
+ }
816
956
IDocument slave = getVisibleDocument ();
817
957
if (slave instanceof ProjectionDocument ) {
818
958
ProjectionDocument projection = (ProjectionDocument ) slave ;
@@ -839,6 +979,11 @@ private void expand(int offset, int length, boolean fireRedraw) throws BadLocati
839
979
}
840
980
}
841
981
982
+ private boolean overlapsWithNonVisibleRegions (int offset , int length ) {
983
+ return fVisibleRegionDuringProjection != null
984
+ && (offset < fVisibleRegionDuringProjection .getOffset () || offset + length > fVisibleRegionDuringProjection .getOffset () + fVisibleRegionDuringProjection .getLength ());
985
+ }
986
+
842
987
/**
843
988
* Processes the request for catch up with the annotation model in the UI thread. If the current
844
989
* thread is not the UI thread or there are pending catch up requests, a new request is posted.
@@ -1054,7 +1199,7 @@ private void processDeletions(AnnotationModelEvent event, Annotation[] removedAn
1054
1199
if (annotation .isCollapsed ()) {
1055
1200
Position expanded = event .getPositionOfRemovedAnnotation (annotation );
1056
1201
if (expanded != null ) {
1057
- expand (expanded .getOffset (), expanded .getLength (), fireRedraw );
1202
+ expand (expanded .getOffset (), expanded .getLength (), fireRedraw , false );
1058
1203
}
1059
1204
}
1060
1205
}
@@ -1158,11 +1303,11 @@ private void processChanges(Annotation[] annotations, boolean fireRedraw, List<P
1158
1303
IRegion [] regions = computeCollapsedRegions (position );
1159
1304
if (regions != null ) {
1160
1305
for (IRegion region : regions ) {
1161
- collapse (region .getOffset (), region .getLength (), fireRedraw );
1306
+ collapse (region .getOffset (), region .getLength (), fireRedraw , false );
1162
1307
}
1163
1308
}
1164
1309
} else {
1165
- expand (position .getOffset (), position .getLength (), fireRedraw );
1310
+ expand (position .getOffset (), position .getLength (), fireRedraw , false );
1166
1311
}
1167
1312
}
1168
1313
}
0 commit comments