11/******************************************************************************* 
2-  * Copyright (c) 2000, 2018  IBM Corporation and others. 
2+  * Copyright (c) 2000, 2025  IBM Corporation and others. 
33 * 
44 * This program and the accompanying materials 
55 * are made available under the terms of the Eclipse Public License 2.0 
3333import  org .eclipse .swt .widgets .Display ;
3434
3535import  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 ;
3639
3740import  org .eclipse .jface .internal .text .SelectionProcessor ;
3841
@@ -277,6 +280,32 @@ private void computeExpectedExecutionCosts() {
277280		}
278281	}
279282
283+ 	/** 
284+ 	 * An {@link IDocumentListener} that makes sure that {@link #fVisibleRegionDuringProjection} is 
285+ 	 * updated when the document changes and ensures that the collapsed region after the visible 
286+ 	 * region is recreated appropriately. 
287+ 	 */ 
288+ 	private  final  class  UpdateDocumentListener  implements  IDocumentListener  {
289+ 		@ Override 
290+ 		public  void  documentChanged (DocumentEvent  event ) {
291+ 			if  (fVisibleRegionDuringProjection  == null ) {
292+ 				return ;
293+ 			}
294+ 			int  oldLength = event .getLength ();
295+ 			int  newLength = event .getText ().length ();
296+ 			int  oldVisibleRegionEnd = fVisibleRegionDuringProjection .getOffset () + fVisibleRegionDuringProjection .getLength ();
297+ 			if  (event .getOffset () < fVisibleRegionDuringProjection .getOffset ()) {
298+ 				fVisibleRegionDuringProjection = new  Region (fVisibleRegionDuringProjection .getOffset () + newLength  - oldLength , fVisibleRegionDuringProjection .getLength ());
299+ 			} else  if  (event .getOffset () + oldLength  <= oldVisibleRegionEnd ) {
300+ 				fVisibleRegionDuringProjection = new  Region (fVisibleRegionDuringProjection .getOffset (), fVisibleRegionDuringProjection .getLength () + newLength  - oldLength );
301+ 			}
302+ 		}
303+ 
304+ 		@ Override 
305+ 		public  void  documentAboutToBeChanged (DocumentEvent  event ) {
306+ 		}
307+ 	}
308+ 
280309	/** The projection annotation model used by this viewer. */ 
281310	private  ProjectionAnnotationModel  fProjectionAnnotationModel ;
282311	/** The annotation model listener */ 
@@ -297,6 +326,11 @@ private void computeExpectedExecutionCosts() {
297326	private  IDocument  fReplaceVisibleDocumentExecutionTrigger ;
298327	/** <code>true</code> if projection was on the last time we switched to segmented mode. */ 
299328	private  boolean  fWasProjectionEnabled ;
329+ 	/** 
330+ 	 * The region set by {@link #setVisibleRegion(int, int)} during projection or <code>null</code> 
331+ 	 * if not in a projection 
332+ 	 */ 
333+ 	private  IRegion  fVisibleRegionDuringProjection ;
300334	/** The queue of projection commands used to assess the costs of projection changes. */ 
301335	private  ProjectionCommandQueue  fCommandQueue ;
302336	/** 
@@ -306,6 +340,7 @@ private void computeExpectedExecutionCosts() {
306340	 */ 
307341	private  int  fDeletedLines ;
308342
343+ 	private  UpdateDocumentListener  fUpdateDocumentListener ;
309344
310345	/** 
311346	 * Creates a new projection source viewer. 
@@ -318,6 +353,7 @@ private void computeExpectedExecutionCosts() {
318353	 */ 
319354	public  ProjectionViewer (Composite  parent , IVerticalRuler  ruler , IOverviewRuler  overviewRuler , boolean  showsAnnotationOverview , int  styles ) {
320355		super (parent , ruler , overviewRuler , showsAnnotationOverview , styles );
356+ 		fUpdateDocumentListener = new  UpdateDocumentListener ();
321357	}
322358
323359	/** 
@@ -514,6 +550,14 @@ public final void disableProjection() {
514550			fProjectionAnnotationModel .removeAllAnnotations ();
515551			fFindReplaceDocumentAdapter = null ;
516552			fireProjectionDisabled ();
553+ 			if  (fVisibleRegionDuringProjection  != null ) {
554+ 				super .setVisibleRegion (fVisibleRegionDuringProjection .getOffset (), fVisibleRegionDuringProjection .getLength ());
555+ 				fVisibleRegionDuringProjection = null ;
556+ 			}
557+ 			IDocument  document = getDocument ();
558+ 			if  (document  != null ) {
559+ 				document .removeDocumentListener (fUpdateDocumentListener );
560+ 			}
517561		}
518562	}
519563
@@ -525,6 +569,15 @@ public final void enableProjection() {
525569			addProjectionAnnotationModel (getVisualAnnotationModel ());
526570			fFindReplaceDocumentAdapter = null ;
527571			fireProjectionEnabled ();
572+ 			IDocument  document = getDocument ();
573+ 			if  (document  == null ) {
574+ 				return ;
575+ 			}
576+ 			IRegion  visibleRegion = getVisibleRegion ();
577+ 			if  (visibleRegion  != null  && (visibleRegion .getOffset () != 0  || visibleRegion .getLength () != 0 ) && visibleRegion .getLength () < document .getLength ()) {
578+ 				setVisibleRegion (visibleRegion .getOffset (), visibleRegion .getLength ());
579+ 			}
580+ 			document .addDocumentListener (fUpdateDocumentListener );
528581		}
529582	}
530583
@@ -533,6 +586,10 @@ private void expandAll() {
533586		IDocument  doc = getDocument ();
534587		int  length = doc  == null  ? 0  : doc .getLength ();
535588		if  (isProjectionMode ()) {
589+ 			if  (fVisibleRegionDuringProjection  != null ) {
590+ 				offset = fVisibleRegionDuringProjection .getOffset ();
591+ 				length = fVisibleRegionDuringProjection .getLength ();
592+ 			}
536593			fProjectionAnnotationModel .expandAll (offset , length );
537594		}
538595	}
@@ -691,9 +748,75 @@ private int toLineStart(IDocument document, int offset, boolean testLastLine) th
691748
692749	@ Override 
693750	public  void  setVisibleRegion (int  start , int  length ) {
694- 		fWasProjectionEnabled = isProjectionMode ();
695- 		disableProjection ();
696- 		super .setVisibleRegion (start , length );
751+ 		if  (!isProjectionMode ()) {
752+ 			super .setVisibleRegion (start , length );
753+ 			return ;
754+ 		}
755+ 		IDocument  document = getDocument ();
756+ 		if  (document  == null ) {
757+ 			return ;
758+ 		}
759+ 		try  {
760+ 			// If the visible region changes, make sure collapsed regions outside of the old visible regions are expanded 
761+ 			// and collapse everything outside the new visible region 
762+ 			int  end = computeEndOfVisibleRegion (start , length , document );
763+ 			expandOutsideCurrentVisibleRegion (document );
764+ 			collapseOutsideOfNewVisibleRegion (start , end , document );
765+ 			fVisibleRegionDuringProjection = new  Region (start , end  - start  - 1 );
766+ 		} catch  (BadLocationException  e ) {
767+ 			ILog  log = ILog .of (getClass ());
768+ 			log .log (new  Status (IStatus .WARNING , getClass (), IStatus .OK , null , e ));
769+ 		}
770+ 	}
771+ 
772+ 	private  void  expandOutsideCurrentVisibleRegion (IDocument  document ) throws  BadLocationException  {
773+ 		if  (fVisibleRegionDuringProjection  != null ) {
774+ 			expand (0 , fVisibleRegionDuringProjection .getOffset (), false , true );
775+ 			int  oldEnd = fVisibleRegionDuringProjection .getOffset () + fVisibleRegionDuringProjection .getLength ();
776+ 			int  length = document .getLength () - oldEnd ;
777+ 			if  (length  > 0 ) {
778+ 				expand (oldEnd , length , false , true );
779+ 			}
780+ 		}
781+ 	}
782+ 
783+ 	private  void  collapseOutsideOfNewVisibleRegion (int  start , int  end , IDocument  document ) throws  BadLocationException  {
784+ 		int  documentLength = document .getLength ();
785+ 		collapse (0 , start , true , true );
786+ 
787+ 		int  endInvisibleRegionLength = documentLength  - end ;
788+ 
789+ 		if  (isLineBreak (document .getChar (documentLength  - 1 ))) {
790+ 			// 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) 
791+ 			endInvisibleRegionLength ++;
792+ 		}
793+ 		if  (endInvisibleRegionLength  > 0 ) {
794+ 			collapse (end , endInvisibleRegionLength , true , true );
795+ 		}
796+ 	}
797+ 
798+ 	private  static  int  computeEndOfVisibleRegion (int  start , int  length , IDocument  document ) throws  BadLocationException  {
799+ 		int  documentLength = document .getLength ();
800+ 		int  end = start  + length  + 1 ;
801+ 		// ensure that trailing whitespace is included 
802+ 		// In this case, the line break needs to be included as well 
803+ 		boolean  visibleRegionEndsWithTrailingWhitespace = end  < documentLength  && isWhitespaceButNotNewline (document .getChar (end  - 1 ));
804+ 		while  (end  < documentLength  && isWhitespaceButNotNewline (document .getChar (end ))) {
805+ 			end ++;
806+ 			visibleRegionEndsWithTrailingWhitespace = true ;
807+ 		}
808+ 		if  (visibleRegionEndsWithTrailingWhitespace  && end  < documentLength  && isLineBreak (document .getChar (end ))) {
809+ 			end ++;
810+ 		}
811+ 		return  end ;
812+ 	}
813+ 
814+ 	private  static  boolean  isWhitespaceButNotNewline (char  c ) {
815+ 		return  Character .isWhitespace (c ) && !isLineBreak (c );
816+ 	}
817+ 
818+ 	private  static  boolean  isLineBreak (char  c ) {
819+ 		return  c  == '\n'  || c  == '\r' ;
697820	}
698821
699822	@ Override 
@@ -719,7 +842,9 @@ public void resetVisibleRegion() {
719842
720843	@ Override 
721844	public  IRegion  getVisibleRegion () {
722- 		disableProjection ();
845+ 		if  (fVisibleRegionDuringProjection  != null ) {
846+ 			return  fVisibleRegionDuringProjection ;
847+ 		}
723848		IRegion  visibleRegion = getModelCoverage ();
724849		if  (visibleRegion  == null ) {
725850			visibleRegion = new  Region (0 , 0 );
@@ -730,7 +855,9 @@ public IRegion getVisibleRegion() {
730855
731856	@ Override 
732857	public  boolean  overlapsWithVisibleRegion (int  offset , int  length ) {
733- 		disableProjection ();
858+ 		if  (fVisibleRegionDuringProjection  != null ) {
859+ 			return  TextUtilities .overlaps (fVisibleRegionDuringProjection , new  Region (offset , length ));
860+ 		}
734861		IRegion  coverage = getModelCoverage ();
735862		if  (coverage  == null ) {
736863			return  false ;
@@ -784,10 +911,16 @@ private void executeReplaceVisibleDocument(IDocument visibleDocument) {
784911	 * 
785912	 * @param offset the offset of the range to hide 
786913	 * @param length the length of the range to hide 
787- 	 * @param fireRedraw <code>true</code> if a redraw request should be issued, <code>false</code> otherwise 
914+ 	 * @param fireRedraw <code>true</code> if a redraw request should be issued, <code>false</code> 
915+ 	 *            otherwise 
916+ 	 * @param performOutsideVisibleRegion <code>true</code> if the range should be collapsed if it 
917+ 	 *            overlaps with anything outside of the visible region, <code>false</code> otherwise 
788918	 * @throws BadLocationException in case the range is invalid 
789919	 */ 
790- 	private  void  collapse (int  offset , int  length , boolean  fireRedraw ) throws  BadLocationException  {
920+ 	private  void  collapse (int  offset , int  length , boolean  fireRedraw , boolean  performOutsideVisibleRegion ) throws  BadLocationException  {
921+ 		if  (!performOutsideVisibleRegion  && overlapsWithNonVisibleRegions (offset , length )) {
922+ 			return ;
923+ 		}
791924		ProjectionDocument  projection = null ;
792925
793926		IDocument  visibleDocument = getVisibleDocument ();
@@ -824,11 +957,16 @@ private void collapse(int offset, int length, boolean fireRedraw) throws BadLoca
824957	 * 
825958	 * @param offset the offset of the range to be expanded 
826959	 * @param length the length of the range to be expanded 
827- 	 * @param fireRedraw <code>true</code> if a redraw request should be issued, 
828- 	 *        <code>false</code> otherwise 
960+ 	 * @param fireRedraw <code>true</code> if a redraw request should be issued, <code>false</code> 
961+ 	 *            otherwise 
962+ 	 * @param performOutsideVisibleRegion <code>true</code> if the range should be collapsed if it 
963+ 	 *            overlaps with anything outside of the visible region, <code>false</code> otherwise 
829964	 * @throws BadLocationException in case the range is invalid 
830965	 */ 
831- 	private  void  expand (int  offset , int  length , boolean  fireRedraw ) throws  BadLocationException  {
966+ 	private  void  expand (int  offset , int  length , boolean  fireRedraw , boolean  performOutsideVisibleRegion ) throws  BadLocationException  {
967+ 		if  (!performOutsideVisibleRegion  && overlapsWithNonVisibleRegions (offset , length )) {
968+ 			return ;
969+ 		}
832970		IDocument  slave = getVisibleDocument ();
833971		if  (slave  instanceof  ProjectionDocument  projection ) {
834972			// expand 
@@ -854,6 +992,11 @@ private void expand(int offset, int length, boolean fireRedraw) throws BadLocati
854992		}
855993	}
856994
995+ 	private  boolean  overlapsWithNonVisibleRegions (int  offset , int  length ) {
996+ 		return  fVisibleRegionDuringProjection  != null 
997+ 				&& (offset  < fVisibleRegionDuringProjection .getOffset () || offset  + length  > fVisibleRegionDuringProjection .getOffset () + fVisibleRegionDuringProjection .getLength ());
998+ 	}
999+ 
8571000	/** 
8581001	 * Processes the request for catch up with the annotation model in the UI thread. If the current 
8591002	 * thread is not the UI thread or there are pending catch up requests, a new request is posted. 
@@ -1074,7 +1217,7 @@ private void processDeletions(AnnotationModelEvent event, Annotation[] removedAn
10741217			if  (annotation .isCollapsed ()) {
10751218				Position  expanded = event .getPositionOfRemovedAnnotation (annotation );
10761219				if  (expanded  != null ) {
1077- 					expand (expanded .getOffset (), expanded .getLength (), fireRedraw );
1220+ 					expand (expanded .getOffset (), expanded .getLength (), fireRedraw ,  false );
10781221				}
10791222			}
10801223		}
@@ -1184,11 +1327,11 @@ private void processChanges(Annotation[] annotations, boolean fireRedraw, List<P
11841327					IRegion [] regions = computeCollapsedRegions (position );
11851328					if  (regions  != null ) {
11861329						for  (IRegion  region  : regions ) {
1187- 							collapse (region .getOffset (), region .getLength (), fireRedraw );
1330+ 							collapse (region .getOffset (), region .getLength (), fireRedraw ,  false );
11881331						}
11891332					}
11901333				} else  {
1191- 					expand (position .getOffset (), position .getLength (), fireRedraw );
1334+ 					expand (position .getOffset (), position .getLength (), fireRedraw ,  false );
11921335				}
11931336			}
11941337		}
0 commit comments