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
2020import java .util .Iterator ;
2121import java .util .List ;
2222
23+ import org .osgi .framework .Bundle ;
24+
2325import org .eclipse .swt .SWTError ;
2426import org .eclipse .swt .custom .ST ;
2527import org .eclipse .swt .custom .StyledText ;
3335import org .eclipse .swt .widgets .Display ;
3436
3537import org .eclipse .core .runtime .Assert ;
38+ import org .eclipse .core .runtime .ILog ;
39+ import org .eclipse .core .runtime .IStatus ;
40+ import org .eclipse .core .runtime .Platform ;
41+ import org .eclipse .core .runtime .Status ;
3642
3743import org .eclipse .jface .internal .text .SelectionProcessor ;
3844
@@ -272,6 +278,32 @@ private void computeExpectedExecutionCosts() {
272278 }
273279 }
274280
281+ /**
282+ * An {@link IDocumentListener} that makes sure that {@link #fVisibleRegionDuringProjection} is
283+ * updated when the document changes and ensures that the collapsed region after the visible
284+ * region is recreated appropriately.
285+ */
286+ private final class UpdateDocumentListener implements IDocumentListener {
287+ @ Override
288+ public void documentChanged (DocumentEvent event ) {
289+ if (fVisibleRegionDuringProjection == null ) {
290+ return ;
291+ }
292+ int oldLength = event .getLength ();
293+ int newLength = event .getText ().length ();
294+ int oldVisibleRegionEnd = fVisibleRegionDuringProjection .getOffset () + fVisibleRegionDuringProjection .getLength ();
295+ if (event .getOffset () < fVisibleRegionDuringProjection .getOffset ()) {
296+ fVisibleRegionDuringProjection = new Region (fVisibleRegionDuringProjection .getOffset () + newLength - oldLength , fVisibleRegionDuringProjection .getLength ());
297+ } else if (event .getOffset () + oldLength <= oldVisibleRegionEnd ) {
298+ fVisibleRegionDuringProjection = new Region (fVisibleRegionDuringProjection .getOffset (), fVisibleRegionDuringProjection .getLength () + newLength - oldLength );
299+ }
300+ }
301+
302+ @ Override
303+ public void documentAboutToBeChanged (DocumentEvent event ) {
304+ }
305+ }
306+
275307 /** The projection annotation model used by this viewer. */
276308 private ProjectionAnnotationModel fProjectionAnnotationModel ;
277309 /** The annotation model listener */
@@ -292,6 +324,11 @@ private void computeExpectedExecutionCosts() {
292324 private IDocument fReplaceVisibleDocumentExecutionTrigger ;
293325 /** <code>true</code> if projection was on the last time we switched to segmented mode. */
294326 private boolean fWasProjectionEnabled ;
327+ /**
328+ * The region set by {@link #setVisibleRegion(int, int)} during projection or <code>null</code>
329+ * if not in a projection
330+ */
331+ private IRegion fVisibleRegionDuringProjection ;
295332 /** The queue of projection commands used to assess the costs of projection changes. */
296333 private ProjectionCommandQueue fCommandQueue ;
297334 /**
@@ -301,6 +338,8 @@ private void computeExpectedExecutionCosts() {
301338 */
302339 private int fDeletedLines ;
303340
341+ private UpdateDocumentListener fUpdateDocumentListener ;
342+
304343
305344 /**
306345 * Creates a new projection source viewer.
@@ -313,6 +352,7 @@ private void computeExpectedExecutionCosts() {
313352 */
314353 public ProjectionViewer (Composite parent , IVerticalRuler ruler , IOverviewRuler overviewRuler , boolean showsAnnotationOverview , int styles ) {
315354 super (parent , ruler , overviewRuler , showsAnnotationOverview , styles );
355+ fUpdateDocumentListener = new UpdateDocumentListener ();
316356 }
317357
318358 /**
@@ -510,6 +550,14 @@ public final void disableProjection() {
510550 fProjectionAnnotationModel .removeAllAnnotations ();
511551 fFindReplaceDocumentAdapter = null ;
512552 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+ }
513561 }
514562 }
515563
@@ -518,9 +566,18 @@ public final void disableProjection() {
518566 */
519567 public final void enableProjection () {
520568 if (!isProjectionMode ()) {
569+ IRegion visibleRegion = getVisibleRegion ();
521570 addProjectionAnnotationModel (getVisualAnnotationModel ());
522571 fFindReplaceDocumentAdapter = null ;
523572 fireProjectionEnabled ();
573+ IDocument document = getDocument ();
574+ if (document == null ) {
575+ return ;
576+ }
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 );
524581 }
525582 }
526583
@@ -529,6 +586,10 @@ private void expandAll() {
529586 IDocument doc = getDocument ();
530587 int length = doc == null ? 0 : doc .getLength ();
531588 if (isProjectionMode ()) {
589+ if (fVisibleRegionDuringProjection != null ) {
590+ offset = fVisibleRegionDuringProjection .getOffset ();
591+ length = fVisibleRegionDuringProjection .getLength ();
592+ }
532593 fProjectionAnnotationModel .expandAll (offset , length );
533594 }
534595 }
@@ -683,9 +744,73 @@ private int toLineStart(IDocument document, int offset, boolean testLastLine) th
683744
684745 @ Override
685746 public void setVisibleRegion (int start , int length ) {
686- fWasProjectionEnabled = isProjectionMode ();
687- disableProjection ();
688- super .setVisibleRegion (start , length );
747+ if (!isProjectionMode ()) {
748+ super .setVisibleRegion (start , length );
749+ return ;
750+ }
751+ IDocument document = getDocument ();
752+ if (document == null ) {
753+ return ;
754+ }
755+ try {
756+ // If the visible region changes, make sure collapsed regions outside of the old visible regions are expanded
757+ // and collapse everything outside the new visible region
758+ int end = computeEndOfVisibleRegion (start , length , document );
759+ expandOutsideCurrentVisibleRegion (document );
760+ collapseOutsideOfNewVisibleRegion (start , end , document );
761+ fVisibleRegionDuringProjection = new Region (start , end - start );
762+ } catch (BadLocationException e ) {
763+ String PLUGIN_ID = "org.eclipse.jface.text" ; //$NON-NLS-1$
764+ Bundle bundle = Platform .getBundle (PLUGIN_ID );
765+ ILog log = ILog .of (bundle );
766+ log .log (new Status (IStatus .WARNING , getClass (), IStatus .OK , e .getMessage (), e ));
767+ }
768+ fVisibleRegionDuringProjection = new Region (start , length );
769+ }
770+
771+ private void expandOutsideCurrentVisibleRegion (IDocument document ) throws BadLocationException {
772+ if (fVisibleRegionDuringProjection != null ) {
773+ expand (0 , fVisibleRegionDuringProjection .getOffset (), false );
774+ int oldEnd = fVisibleRegionDuringProjection .getOffset () + fVisibleRegionDuringProjection .getLength ();
775+ int length = document .getLength () - oldEnd ;
776+ if (length > 0 ) {
777+ expand (oldEnd , length , false );
778+ }
779+ }
780+ }
781+
782+ private void collapseOutsideOfNewVisibleRegion (int start , int end , IDocument document ) throws BadLocationException {
783+ int documentLength = document .getLength ();
784+ collapse (0 , start , true );
785+
786+ int endInvisibleRegionLength = documentLength - end ;
787+ if (endInvisibleRegionLength > 0 ) {
788+ collapse (end , endInvisibleRegionLength , true );
789+ }
790+ }
791+
792+ private static int computeEndOfVisibleRegion (int start , int length , IDocument document ) throws BadLocationException {
793+ int documentLength = document .getLength ();
794+ int end = start + length + 1 ;
795+ // ensure that trailing whitespace is included
796+ // In this case, the line break needs to be included as well
797+ boolean visibleRegionEndsWithTrailingWhitespace = end < documentLength && isWhitespaceButNotNewline (document .getChar (end - 1 ));
798+ while (end < documentLength && isWhitespaceButNotNewline (document .getChar (end ))) {
799+ end ++;
800+ visibleRegionEndsWithTrailingWhitespace = true ;
801+ }
802+ if (visibleRegionEndsWithTrailingWhitespace && end < documentLength && isLineBreak (document .getChar (end ))) {
803+ end ++;
804+ }
805+ return end ;
806+ }
807+
808+ private static boolean isWhitespaceButNotNewline (char c ) {
809+ return Character .isWhitespace (c ) && !isLineBreak (c );
810+ }
811+
812+ private static boolean isLineBreak (char c ) {
813+ return c == '\n' || c == '\r' ;
689814 }
690815
691816 @ Override
@@ -710,6 +835,9 @@ public void resetVisibleRegion() {
710835
711836 @ Override
712837 public IRegion getVisibleRegion () {
838+ if (fVisibleRegionDuringProjection != null ) {
839+ return fVisibleRegionDuringProjection ;
840+ }
713841 disableProjection ();
714842 IRegion visibleRegion = getModelCoverage ();
715843 if (visibleRegion == null )
0 commit comments