Skip to content

Commit bdd39d0

Browse files
committed
Allow using visible regions with projections #3073
While ProjectionViewer supports both using visible regions and projections, these features cannot be used in conjunction. This change allows the use of projections when visible regions are used. Fixes #3074
1 parent 789b373 commit bdd39d0

File tree

2 files changed

+400
-5
lines changed

2 files changed

+400
-5
lines changed

bundles/org.eclipse.jface.text/projection/org/eclipse/jface/text/source/projection/ProjectionViewer.java

Lines changed: 132 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
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
@@ -20,6 +20,8 @@
2020
import java.util.Iterator;
2121
import java.util.List;
2222

23+
import org.osgi.framework.Bundle;
24+
2325
import org.eclipse.swt.SWTError;
2426
import org.eclipse.swt.custom.ST;
2527
import org.eclipse.swt.custom.StyledText;
@@ -33,6 +35,10 @@
3335
import org.eclipse.swt.widgets.Display;
3436

3537
import 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

3743
import 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

Comments
 (0)