Skip to content

Commit 4327dc9

Browse files
committed
Allow using visible regions with projections eclipse-platform#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 eclipse-platform#3074
1 parent 789b373 commit 4327dc9

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= 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)