Skip to content

Commit a783bc4

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 a783bc4

File tree

2 files changed

+375
-5
lines changed

2 files changed

+375
-5
lines changed

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

Lines changed: 107 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
@@ -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;
338+
304339

305340
/**
306341
* Creates a new projection source viewer.
@@ -313,6 +348,7 @@ private void computeExpectedExecutionCosts() {
313348
*/
314349
public ProjectionViewer(Composite parent, IVerticalRuler ruler, IOverviewRuler overviewRuler, boolean showsAnnotationOverview, int styles) {
315350
super(parent, ruler, overviewRuler, showsAnnotationOverview, styles);
351+
fUpdateDocumentListener= new UpdateDocumentListener();
316352
}
317353

318354
/**
@@ -510,6 +546,14 @@ public final void disableProjection() {
510546
fProjectionAnnotationModel.removeAllAnnotations();
511547
fFindReplaceDocumentAdapter= null;
512548
fireProjectionDisabled();
549+
if (fVisibleRegionDuringProjection != null) {
550+
super.setVisibleRegion(fVisibleRegionDuringProjection.getOffset(), fVisibleRegionDuringProjection.getLength());
551+
fVisibleRegionDuringProjection= null;
552+
}
553+
IDocument document= getDocument();
554+
if (document != null) {
555+
document.removeDocumentListener(fUpdateDocumentListener);
556+
}
513557
}
514558
}
515559

@@ -518,9 +562,18 @@ public final void disableProjection() {
518562
*/
519563
public final void enableProjection() {
520564
if (!isProjectionMode()) {
565+
IRegion visibleRegion= getVisibleRegion();
521566
addProjectionAnnotationModel(getVisualAnnotationModel());
522567
fFindReplaceDocumentAdapter= null;
523568
fireProjectionEnabled();
569+
IDocument document= getDocument();
570+
if (document == null) {
571+
return;
572+
}
573+
if (visibleRegion != null && (visibleRegion.getOffset() != 0 || visibleRegion.getLength() != 0) && visibleRegion.getLength() < document.getLength()) {
574+
setVisibleRegion(visibleRegion.getOffset(), visibleRegion.getLength());
575+
}
576+
document.addDocumentListener(fUpdateDocumentListener);
524577
}
525578
}
526579

@@ -529,6 +582,10 @@ private void expandAll() {
529582
IDocument doc= getDocument();
530583
int length= doc == null ? 0 : doc.getLength();
531584
if (isProjectionMode()) {
585+
if (fVisibleRegionDuringProjection != null) {
586+
offset= fVisibleRegionDuringProjection.getOffset();
587+
length= fVisibleRegionDuringProjection.getLength();
588+
}
532589
fProjectionAnnotationModel.expandAll(offset, length);
533590
}
534591
}
@@ -683,9 +740,52 @@ private int toLineStart(IDocument document, int offset, boolean testLastLine) th
683740

684741
@Override
685742
public void setVisibleRegion(int start, int length) {
686-
fWasProjectionEnabled= isProjectionMode();
687-
disableProjection();
688-
super.setVisibleRegion(start, length);
743+
if (!isProjectionMode()) {
744+
super.setVisibleRegion(start, length);
745+
return;
746+
}
747+
IDocument document= getDocument();
748+
if (document == null) {
749+
return;
750+
}
751+
try {
752+
int documentLength= document.getLength();
753+
if (fVisibleRegionDuringProjection != null) {
754+
expand(0, fVisibleRegionDuringProjection.getOffset(), false);
755+
int oldEnd= fVisibleRegionDuringProjection.getOffset() + fVisibleRegionDuringProjection.getLength();
756+
expand(oldEnd, documentLength - oldEnd, false);
757+
}
758+
collapse(0, start, true);
759+
760+
int end= start + length + 1;
761+
// ensure that trailing whitespace is included
762+
// In this case, the line break needs to be included as well
763+
boolean visibleRegionEndsWithTrailingWhitespace= isWhitespaceButNotNewline(document.getChar(end - 1));
764+
while (end < documentLength && isWhitespaceButNotNewline(document.getChar(end))) {
765+
end++;
766+
visibleRegionEndsWithTrailingWhitespace= true;
767+
}
768+
if (visibleRegionEndsWithTrailingWhitespace && end < documentLength && isLineBreak(document.getChar(end))) {
769+
end++;
770+
}
771+
772+
int endInvisibleRegionLength= documentLength - end;
773+
if (endInvisibleRegionLength > 0) {
774+
collapse(end, endInvisibleRegionLength, true);
775+
}
776+
fVisibleRegionDuringProjection= new Region(start, end - start);
777+
} catch (BadLocationException e) {
778+
e.printStackTrace();
779+
}
780+
fVisibleRegionDuringProjection= new Region(start, length);
781+
}
782+
783+
private boolean isWhitespaceButNotNewline(char c) {
784+
return Character.isWhitespace(c) && !isLineBreak(c);
785+
}
786+
787+
private boolean isLineBreak(char c) {
788+
return c == '\n' || c == '\r';
689789
}
690790

691791
@Override
@@ -710,6 +810,9 @@ public void resetVisibleRegion() {
710810

711811
@Override
712812
public IRegion getVisibleRegion() {
813+
if (fVisibleRegionDuringProjection != null) {
814+
return fVisibleRegionDuringProjection;
815+
}
713816
disableProjection();
714817
IRegion visibleRegion= getModelCoverage();
715818
if (visibleRegion == null)

0 commit comments

Comments
 (0)