Skip to content

Commit c5c2977

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 c5c2977

File tree

2 files changed

+421
-6
lines changed

2 files changed

+421
-6
lines changed

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

Lines changed: 126 additions & 5 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
@@ -33,6 +33,9 @@
3333
import org.eclipse.swt.widgets.Display;
3434

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

3740
import org.eclipse.jface.internal.text.SelectionProcessor;
3841

@@ -272,6 +275,32 @@ private void computeExpectedExecutionCosts() {
272275
}
273276
}
274277

278+
/**
279+
* An {@link IDocumentListener} that makes sure that {@link #fVisibleRegionDuringProjection} is
280+
* updated when the document changes and ensures that the collapsed region after the visible
281+
* region is recreated appropriately.
282+
*/
283+
private final class UpdateDocumentListener implements IDocumentListener {
284+
@Override
285+
public void documentChanged(DocumentEvent event) {
286+
if (fVisibleRegionDuringProjection == null) {
287+
return;
288+
}
289+
int oldLength= event.getLength();
290+
int newLength= event.getText().length();
291+
int oldVisibleRegionEnd= fVisibleRegionDuringProjection.getOffset() + fVisibleRegionDuringProjection.getLength();
292+
if (event.getOffset() < fVisibleRegionDuringProjection.getOffset()) {
293+
fVisibleRegionDuringProjection= new Region(fVisibleRegionDuringProjection.getOffset() + newLength - oldLength, fVisibleRegionDuringProjection.getLength());
294+
} else if (event.getOffset() + oldLength <= oldVisibleRegionEnd) {
295+
fVisibleRegionDuringProjection= new Region(fVisibleRegionDuringProjection.getOffset(), fVisibleRegionDuringProjection.getLength() + newLength - oldLength);
296+
}
297+
}
298+
299+
@Override
300+
public void documentAboutToBeChanged(DocumentEvent event) {
301+
}
302+
}
303+
275304
/** The projection annotation model used by this viewer. */
276305
private ProjectionAnnotationModel fProjectionAnnotationModel;
277306
/** The annotation model listener */
@@ -292,6 +321,11 @@ private void computeExpectedExecutionCosts() {
292321
private IDocument fReplaceVisibleDocumentExecutionTrigger;
293322
/** <code>true</code> if projection was on the last time we switched to segmented mode. */
294323
private boolean fWasProjectionEnabled;
324+
/**
325+
* The region set by {@link #setVisibleRegion(int, int)} during projection or <code>null</code>
326+
* if not in a projection
327+
*/
328+
private IRegion fVisibleRegionDuringProjection;
295329
/** The queue of projection commands used to assess the costs of projection changes. */
296330
private ProjectionCommandQueue fCommandQueue;
297331
/**
@@ -301,6 +335,8 @@ private void computeExpectedExecutionCosts() {
301335
*/
302336
private int fDeletedLines;
303337

338+
private UpdateDocumentListener fUpdateDocumentListener;
339+
304340

305341
/**
306342
* Creates a new projection source viewer.
@@ -313,6 +349,7 @@ private void computeExpectedExecutionCosts() {
313349
*/
314350
public ProjectionViewer(Composite parent, IVerticalRuler ruler, IOverviewRuler overviewRuler, boolean showsAnnotationOverview, int styles) {
315351
super(parent, ruler, overviewRuler, showsAnnotationOverview, styles);
352+
fUpdateDocumentListener= new UpdateDocumentListener();
316353
}
317354

318355
/**
@@ -510,6 +547,14 @@ public final void disableProjection() {
510547
fProjectionAnnotationModel.removeAllAnnotations();
511548
fFindReplaceDocumentAdapter= null;
512549
fireProjectionDisabled();
550+
if (fVisibleRegionDuringProjection != null) {
551+
super.setVisibleRegion(fVisibleRegionDuringProjection.getOffset(), fVisibleRegionDuringProjection.getLength());
552+
fVisibleRegionDuringProjection= null;
553+
}
554+
IDocument document= getDocument();
555+
if (document != null) {
556+
document.removeDocumentListener(fUpdateDocumentListener);
557+
}
513558
}
514559
}
515560

@@ -521,6 +566,15 @@ public final void enableProjection() {
521566
addProjectionAnnotationModel(getVisualAnnotationModel());
522567
fFindReplaceDocumentAdapter= null;
523568
fireProjectionEnabled();
569+
IDocument document= getDocument();
570+
if (document == null) {
571+
return;
572+
}
573+
IRegion visibleRegion= getVisibleRegion();
574+
if (visibleRegion != null && (visibleRegion.getOffset() != 0 || visibleRegion.getLength() != 0) && visibleRegion.getLength() < document.getLength()) {
575+
setVisibleRegion(visibleRegion.getOffset(), visibleRegion.getLength());
576+
}
577+
document.addDocumentListener(fUpdateDocumentListener);
524578
}
525579
}
526580

@@ -529,6 +583,10 @@ private void expandAll() {
529583
IDocument doc= getDocument();
530584
int length= doc == null ? 0 : doc.getLength();
531585
if (isProjectionMode()) {
586+
if (fVisibleRegionDuringProjection != null) {
587+
offset= fVisibleRegionDuringProjection.getOffset();
588+
length= fVisibleRegionDuringProjection.getLength();
589+
}
532590
fProjectionAnnotationModel.expandAll(offset, length);
533591
}
534592
}
@@ -683,9 +741,70 @@ private int toLineStart(IDocument document, int offset, boolean testLastLine) th
683741

684742
@Override
685743
public void setVisibleRegion(int start, int length) {
686-
fWasProjectionEnabled= isProjectionMode();
687-
disableProjection();
688-
super.setVisibleRegion(start, length);
744+
if (!isProjectionMode()) {
745+
super.setVisibleRegion(start, length);
746+
return;
747+
}
748+
IDocument document= getDocument();
749+
if (document == null) {
750+
return;
751+
}
752+
try {
753+
// If the visible region changes, make sure collapsed regions outside of the old visible regions are expanded
754+
// and collapse everything outside the new visible region
755+
int end= computeEndOfVisibleRegion(start, length, document);
756+
expandOutsideCurrentVisibleRegion(document);
757+
collapseOutsideOfNewVisibleRegion(start, end, document);
758+
fVisibleRegionDuringProjection= new Region(start, end - start - 1);
759+
} catch (BadLocationException e) {
760+
ILog log= ILog.of(getClass());
761+
log.log(new Status(IStatus.WARNING, getClass(), IStatus.OK, null, e));
762+
}
763+
}
764+
765+
private void expandOutsideCurrentVisibleRegion(IDocument document) throws BadLocationException {
766+
if (fVisibleRegionDuringProjection != null) {
767+
expand(0, fVisibleRegionDuringProjection.getOffset(), false);
768+
int oldEnd= fVisibleRegionDuringProjection.getOffset() + fVisibleRegionDuringProjection.getLength();
769+
int length= document.getLength() - oldEnd;
770+
if (length > 0) {
771+
expand(oldEnd, length, false);
772+
}
773+
}
774+
}
775+
776+
private void collapseOutsideOfNewVisibleRegion(int start, int end, IDocument document) throws BadLocationException {
777+
int documentLength= document.getLength();
778+
collapse(0, start, true);
779+
780+
int endInvisibleRegionLength= documentLength - end;
781+
if (endInvisibleRegionLength > 0) {
782+
collapse(end, endInvisibleRegionLength, true);
783+
}
784+
}
785+
786+
private static int computeEndOfVisibleRegion(int start, int length, IDocument document) throws BadLocationException {
787+
int documentLength= document.getLength();
788+
int end= start + length + 1;
789+
// ensure that trailing whitespace is included
790+
// In this case, the line break needs to be included as well
791+
boolean visibleRegionEndsWithTrailingWhitespace= end < documentLength && isWhitespaceButNotNewline(document.getChar(end - 1));
792+
while (end < documentLength && isWhitespaceButNotNewline(document.getChar(end))) {
793+
end++;
794+
visibleRegionEndsWithTrailingWhitespace= true;
795+
}
796+
if (visibleRegionEndsWithTrailingWhitespace && end < documentLength && isLineBreak(document.getChar(end))) {
797+
end++;
798+
}
799+
return end;
800+
}
801+
802+
private static boolean isWhitespaceButNotNewline(char c) {
803+
return Character.isWhitespace(c) && !isLineBreak(c);
804+
}
805+
806+
private static boolean isLineBreak(char c) {
807+
return c == '\n' || c == '\r';
689808
}
690809

691810
@Override
@@ -710,7 +829,9 @@ public void resetVisibleRegion() {
710829

711830
@Override
712831
public IRegion getVisibleRegion() {
713-
disableProjection();
832+
if (fVisibleRegionDuringProjection != null) {
833+
return fVisibleRegionDuringProjection;
834+
}
714835
IRegion visibleRegion= getModelCoverage();
715836
if (visibleRegion == null)
716837
visibleRegion= new Region(0, 0);

0 commit comments

Comments
 (0)