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
@@ -272,6 +272,34 @@ private void computeExpectedExecutionCosts() {
272272 }
273273 }
274274
275+ /**
276+ * An {@link IDocumentListener} that makes sure that {@link #fVisibleRegionDuringProjection} is
277+ * updated when the document changes and ensures that the collapsed region after the visible
278+ * region is recreated appropriately.
279+ */
280+ private final class UpdateDocumentListener implements IDocumentListener {
281+ @ Override
282+ public void documentChanged (DocumentEvent event ) {
283+ if (fVisibleRegionDuringProjection != null ) {
284+ int oldLength = event .getLength ();
285+ int newLength = event .getText ().length ();
286+ int oldVisibleRegionEnd = fVisibleRegionDuringProjection .getOffset () + fVisibleRegionDuringProjection .getLength ();
287+
288+ if (event .getOffset () < fVisibleRegionDuringProjection .getOffset ()) {
289+ fVisibleRegionDuringProjection = new Region (fVisibleRegionDuringProjection .getOffset () + newLength - oldLength , fVisibleRegionDuringProjection .getLength ());
290+ } else {
291+ if (event .getOffset () + oldLength < oldVisibleRegionEnd ) {
292+ fVisibleRegionDuringProjection = new Region (fVisibleRegionDuringProjection .getOffset (), fVisibleRegionDuringProjection .getLength () + newLength - oldLength );
293+ }
294+ }
295+ }
296+ }
297+
298+ @ Override
299+ public void documentAboutToBeChanged (DocumentEvent event ) {
300+ }
301+ }
302+
275303 /** The projection annotation model used by this viewer. */
276304 private ProjectionAnnotationModel fProjectionAnnotationModel ;
277305 /** The annotation model listener */
@@ -292,6 +320,11 @@ private void computeExpectedExecutionCosts() {
292320 private IDocument fReplaceVisibleDocumentExecutionTrigger ;
293321 /** <code>true</code> if projection was on the last time we switched to segmented mode. */
294322 private boolean fWasProjectionEnabled ;
323+ /**
324+ * The region set by {@link #setVisibleRegion(int, int)} during projection or <code>null</code>
325+ * if not in a projection
326+ */
327+ private IRegion fVisibleRegionDuringProjection ;
295328 /** The queue of projection commands used to assess the costs of projection changes. */
296329 private ProjectionCommandQueue fCommandQueue ;
297330 /**
@@ -301,6 +334,8 @@ private void computeExpectedExecutionCosts() {
301334 */
302335 private int fDeletedLines ;
303336
337+ private UpdateDocumentListener fUpdateDocumentListener = new UpdateDocumentListener ();
338+
304339
305340 /**
306341 * Creates a new projection source viewer.
@@ -510,6 +545,11 @@ public final void disableProjection() {
510545 fProjectionAnnotationModel .removeAllAnnotations ();
511546 fFindReplaceDocumentAdapter = null ;
512547 fireProjectionDisabled ();
548+ if (fVisibleRegionDuringProjection != null ) {
549+ super .setVisibleRegion (fVisibleRegionDuringProjection .getOffset (), fVisibleRegionDuringProjection .getLength ());
550+ fVisibleRegionDuringProjection = null ;
551+ }
552+ getDocument ().removeDocumentListener (fUpdateDocumentListener );
513553 }
514554 }
515555
@@ -518,9 +558,14 @@ public final void disableProjection() {
518558 */
519559 public final void enableProjection () {
520560 if (!isProjectionMode ()) {
561+ IRegion visibleRegion = getVisibleRegion ();
521562 addProjectionAnnotationModel (getVisualAnnotationModel ());
522563 fFindReplaceDocumentAdapter = null ;
523564 fireProjectionEnabled ();
565+ if (visibleRegion != null && (visibleRegion .getOffset () != 0 || visibleRegion .getLength () != 0 ) && visibleRegion .getLength () < getDocument ().getLength ()) {
566+ setVisibleRegion (visibleRegion .getOffset (), visibleRegion .getLength ());
567+ }
568+ getDocument ().addDocumentListener (fUpdateDocumentListener );
524569 }
525570 }
526571
@@ -529,6 +574,10 @@ private void expandAll() {
529574 IDocument doc = getDocument ();
530575 int length = doc == null ? 0 : doc .getLength ();
531576 if (isProjectionMode ()) {
577+ if (fVisibleRegionDuringProjection != null ) {
578+ offset = fVisibleRegionDuringProjection .getOffset ();
579+ length = fVisibleRegionDuringProjection .getLength ();
580+ }
532581 fProjectionAnnotationModel .expandAll (offset , length );
533582 }
534583 }
@@ -683,9 +732,48 @@ private int toLineStart(IDocument document, int offset, boolean testLastLine) th
683732
684733 @ Override
685734 public void setVisibleRegion (int start , int length ) {
686- fWasProjectionEnabled = isProjectionMode ();
687- disableProjection ();
688- super .setVisibleRegion (start , length );
735+ if (isProjectionMode ()) {
736+ try {
737+ int documentLength = getDocument ().getLength ();
738+ if (fVisibleRegionDuringProjection != null ) {
739+ expand (0 , fVisibleRegionDuringProjection .getOffset (), false );
740+ int oldEnd = fVisibleRegionDuringProjection .getOffset () + fVisibleRegionDuringProjection .getLength ();
741+ expand (oldEnd , documentLength - oldEnd , false );
742+ }
743+ collapse (0 , start , true );
744+
745+ int end = start + length + 1 ;
746+ // ensure that trailing whitespace is included
747+ // In this case, the line break needs to be included as well
748+ boolean movedDueToTrailingWhitespace = false ;
749+ while (end < documentLength && isWhitespaceButNotNewline (getDocument ().getChar (end ))) {
750+ end ++;
751+ movedDueToTrailingWhitespace = true ;
752+ }
753+ if (movedDueToTrailingWhitespace && end < documentLength && isLineBreak (getDocument ().getChar (end ))) {
754+ end ++;
755+ }
756+
757+ int endInvisibleRegionLength = documentLength - end ;
758+ if (endInvisibleRegionLength > 0 ) {
759+ collapse (end , endInvisibleRegionLength , true );
760+ }
761+ fVisibleRegionDuringProjection = new Region (start , end - start );
762+ } catch (BadLocationException e ) {
763+ e .printStackTrace ();
764+ }
765+ fVisibleRegionDuringProjection = new Region (start , length );
766+ } else {
767+ super .setVisibleRegion (start , length );
768+ }
769+ }
770+
771+ private boolean isWhitespaceButNotNewline (char c ) {
772+ return Character .isWhitespace (c ) && !isLineBreak (c );
773+ }
774+
775+ private boolean isLineBreak (char c ) {
776+ return c == '\n' || c == '\r' ;
689777 }
690778
691779 @ Override
@@ -710,6 +798,9 @@ public void resetVisibleRegion() {
710798
711799 @ Override
712800 public IRegion getVisibleRegion () {
801+ if (fVisibleRegionDuringProjection != null ) {
802+ return fVisibleRegionDuringProjection ;
803+ }
713804 disableProjection ();
714805 IRegion visibleRegion = getModelCoverage ();
715806 if (visibleRegion == null )
0 commit comments