Skip to content

Commit 5a493ee

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 5a493ee

File tree

2 files changed

+363
-5
lines changed

2 files changed

+363
-5
lines changed

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

Lines changed: 95 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= 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

Comments
 (0)