Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2000, 2018 IBM Corporation and others.
* Copyright (c) 2000, 2025 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
Expand Down Expand Up @@ -33,6 +33,9 @@
import org.eclipse.swt.widgets.Display;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.ILog;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;

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

Expand Down Expand Up @@ -272,6 +275,32 @@ private void computeExpectedExecutionCosts() {
}
}

/**
* An {@link IDocumentListener} that makes sure that {@link #fVisibleRegionDuringProjection} is
* updated when the document changes and ensures that the collapsed region after the visible
* region is recreated appropriately.
*/
private final class UpdateDocumentListener implements IDocumentListener {
@Override
public void documentChanged(DocumentEvent event) {
if (fVisibleRegionDuringProjection == null) {
return;
}
int oldLength= event.getLength();
int newLength= event.getText().length();
int oldVisibleRegionEnd= fVisibleRegionDuringProjection.getOffset() + fVisibleRegionDuringProjection.getLength();
if (event.getOffset() < fVisibleRegionDuringProjection.getOffset()) {
fVisibleRegionDuringProjection= new Region(fVisibleRegionDuringProjection.getOffset() + newLength - oldLength, fVisibleRegionDuringProjection.getLength());
} else if (event.getOffset() + oldLength <= oldVisibleRegionEnd) {
fVisibleRegionDuringProjection= new Region(fVisibleRegionDuringProjection.getOffset(), fVisibleRegionDuringProjection.getLength() + newLength - oldLength);
}
}

@Override
public void documentAboutToBeChanged(DocumentEvent event) {
}
}

/** The projection annotation model used by this viewer. */
private ProjectionAnnotationModel fProjectionAnnotationModel;
/** The annotation model listener */
Expand All @@ -292,6 +321,11 @@ private void computeExpectedExecutionCosts() {
private IDocument fReplaceVisibleDocumentExecutionTrigger;
/** <code>true</code> if projection was on the last time we switched to segmented mode. */
private boolean fWasProjectionEnabled;
/**
* The region set by {@link #setVisibleRegion(int, int)} during projection or <code>null</code>
* if not in a projection
*/
private IRegion fVisibleRegionDuringProjection;
/** The queue of projection commands used to assess the costs of projection changes. */
private ProjectionCommandQueue fCommandQueue;
/**
Expand All @@ -301,6 +335,8 @@ private void computeExpectedExecutionCosts() {
*/
private int fDeletedLines;

private UpdateDocumentListener fUpdateDocumentListener;


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

/**
Expand Down Expand Up @@ -510,6 +547,14 @@ public final void disableProjection() {
fProjectionAnnotationModel.removeAllAnnotations();
fFindReplaceDocumentAdapter= null;
fireProjectionDisabled();
if (fVisibleRegionDuringProjection != null) {
super.setVisibleRegion(fVisibleRegionDuringProjection.getOffset(), fVisibleRegionDuringProjection.getLength());
fVisibleRegionDuringProjection= null;
}
IDocument document= getDocument();
if (document != null) {
document.removeDocumentListener(fUpdateDocumentListener);
}
}
}

Expand All @@ -521,6 +566,15 @@ public final void enableProjection() {
addProjectionAnnotationModel(getVisualAnnotationModel());
fFindReplaceDocumentAdapter= null;
fireProjectionEnabled();
IDocument document= getDocument();
if (document == null) {
return;
}
IRegion visibleRegion= getVisibleRegion();
if (visibleRegion != null && (visibleRegion.getOffset() != 0 || visibleRegion.getLength() != 0) && visibleRegion.getLength() < document.getLength()) {
setVisibleRegion(visibleRegion.getOffset(), visibleRegion.getLength());
}
document.addDocumentListener(fUpdateDocumentListener);
}
}

Expand All @@ -529,6 +583,10 @@ private void expandAll() {
IDocument doc= getDocument();
int length= doc == null ? 0 : doc.getLength();
if (isProjectionMode()) {
if (fVisibleRegionDuringProjection != null) {
offset= fVisibleRegionDuringProjection.getOffset();
length= fVisibleRegionDuringProjection.getLength();
}
fProjectionAnnotationModel.expandAll(offset, length);
}
}
Expand Down Expand Up @@ -683,9 +741,75 @@ private int toLineStart(IDocument document, int offset, boolean testLastLine) th

@Override
public void setVisibleRegion(int start, int length) {
fWasProjectionEnabled= isProjectionMode();
disableProjection();
super.setVisibleRegion(start, length);
if (!isProjectionMode()) {
super.setVisibleRegion(start, length);
return;
}
IDocument document= getDocument();
if (document == null) {
return;
}
try {
// If the visible region changes, make sure collapsed regions outside of the old visible regions are expanded
// and collapse everything outside the new visible region
int end= computeEndOfVisibleRegion(start, length, document);
expandOutsideCurrentVisibleRegion(document);
collapseOutsideOfNewVisibleRegion(start, end, document);
fVisibleRegionDuringProjection= new Region(start, end - start - 1);
} catch (BadLocationException e) {
ILog log= ILog.of(getClass());
log.log(new Status(IStatus.WARNING, getClass(), IStatus.OK, null, e));
}
}

private void expandOutsideCurrentVisibleRegion(IDocument document) throws BadLocationException {
if (fVisibleRegionDuringProjection != null) {
expand(0, fVisibleRegionDuringProjection.getOffset(), false, true);
int oldEnd= fVisibleRegionDuringProjection.getOffset() + fVisibleRegionDuringProjection.getLength();
int length= document.getLength() - oldEnd;
if (length > 0) {
expand(oldEnd, length, false, true);
}
}
}

private void collapseOutsideOfNewVisibleRegion(int start, int end, IDocument document) throws BadLocationException {
int documentLength= document.getLength();
collapse(0, start, true, true);

int endInvisibleRegionLength= documentLength - end;

if (isLineBreak(document.getChar(documentLength - 1))) {
// if the file ends with an empty line, make sure it is included as well (ensuring the user doesn't accidentially remove parts outside the visible region)
endInvisibleRegionLength++;
}
if (endInvisibleRegionLength > 0) {
collapse(end, endInvisibleRegionLength, true, true);
}
}

private static int computeEndOfVisibleRegion(int start, int length, IDocument document) throws BadLocationException {
int documentLength= document.getLength();
int end= start + length + 1;
// ensure that trailing whitespace is included
// In this case, the line break needs to be included as well
boolean visibleRegionEndsWithTrailingWhitespace= end < documentLength && isWhitespaceButNotNewline(document.getChar(end - 1));
while (end < documentLength && isWhitespaceButNotNewline(document.getChar(end))) {
end++;
visibleRegionEndsWithTrailingWhitespace= true;
}
if (visibleRegionEndsWithTrailingWhitespace && end < documentLength && isLineBreak(document.getChar(end))) {
end++;
}
return end;
}

private static boolean isWhitespaceButNotNewline(char c) {
return Character.isWhitespace(c) && !isLineBreak(c);
}

private static boolean isLineBreak(char c) {
return c == '\n' || c == '\r';
}

@Override
Expand All @@ -710,7 +834,9 @@ public void resetVisibleRegion() {

@Override
public IRegion getVisibleRegion() {
disableProjection();
if (fVisibleRegionDuringProjection != null) {
return fVisibleRegionDuringProjection;
}
IRegion visibleRegion= getModelCoverage();
if (visibleRegion == null)
visibleRegion= new Region(0, 0);
Expand All @@ -720,7 +846,9 @@ public IRegion getVisibleRegion() {

@Override
public boolean overlapsWithVisibleRegion(int offset, int length) {
disableProjection();
if (fVisibleRegionDuringProjection != null) {
return TextUtilities.overlaps(fVisibleRegionDuringProjection, new Region(offset, length));
}
IRegion coverage= getModelCoverage();
if (coverage == null)
return false;
Expand Down Expand Up @@ -769,10 +897,16 @@ private void executeReplaceVisibleDocument(IDocument visibleDocument) {
*
* @param offset the offset of the range to hide
* @param length the length of the range to hide
* @param fireRedraw <code>true</code> if a redraw request should be issued, <code>false</code> otherwise
* @param fireRedraw <code>true</code> if a redraw request should be issued, <code>false</code>
* otherwise
* @param performOutsideVisibleRegion <code>true</code> if the range should be collapsed if it
* overlaps with anything outside of the visible region, <code>false</code> otherwise
* @throws BadLocationException in case the range is invalid
*/
private void collapse(int offset, int length, boolean fireRedraw) throws BadLocationException {
private void collapse(int offset, int length, boolean fireRedraw, boolean performOutsideVisibleRegion) throws BadLocationException {
if (!performOutsideVisibleRegion && overlapsWithNonVisibleRegions(offset, length)) {
return;
}
ProjectionDocument projection= null;

IDocument visibleDocument= getVisibleDocument();
Expand Down Expand Up @@ -802,17 +936,23 @@ private void collapse(int offset, int length, boolean fireRedraw) throws BadLoca
}
}


/**
* Makes the given range visible again while not changing the folding state of any contained
* ranges. If requested, a redraw request is issued.
*
* @param offset the offset of the range to be expanded
* @param length the length of the range to be expanded
* @param fireRedraw <code>true</code> if a redraw request should be issued,
* <code>false</code> otherwise
* @param fireRedraw <code>true</code> if a redraw request should be issued, <code>false</code>
* otherwise
* @param performOutsideVisibleRegion <code>true</code> if the range should be collapsed if it
* overlaps with anything outside of the visible region, <code>false</code> otherwise
* @throws BadLocationException in case the range is invalid
*/
private void expand(int offset, int length, boolean fireRedraw) throws BadLocationException {
private void expand(int offset, int length, boolean fireRedraw, boolean performOutsideVisibleRegion) throws BadLocationException {
if (!performOutsideVisibleRegion && overlapsWithNonVisibleRegions(offset, length)) {
return;
}
IDocument slave= getVisibleDocument();
if (slave instanceof ProjectionDocument) {
ProjectionDocument projection= (ProjectionDocument) slave;
Expand All @@ -839,6 +979,11 @@ private void expand(int offset, int length, boolean fireRedraw) throws BadLocati
}
}

private boolean overlapsWithNonVisibleRegions(int offset, int length) {
return fVisibleRegionDuringProjection != null
&& (offset < fVisibleRegionDuringProjection.getOffset() || offset + length > fVisibleRegionDuringProjection.getOffset() + fVisibleRegionDuringProjection.getLength());
}

/**
* Processes the request for catch up with the annotation model in the UI thread. If the current
* thread is not the UI thread or there are pending catch up requests, a new request is posted.
Expand Down Expand Up @@ -1054,7 +1199,7 @@ private void processDeletions(AnnotationModelEvent event, Annotation[] removedAn
if (annotation.isCollapsed()) {
Position expanded= event.getPositionOfRemovedAnnotation(annotation);
if (expanded != null) {
expand(expanded.getOffset(), expanded.getLength(), fireRedraw);
expand(expanded.getOffset(), expanded.getLength(), fireRedraw, false);
}
}
}
Expand Down Expand Up @@ -1158,11 +1303,11 @@ private void processChanges(Annotation[] annotations, boolean fireRedraw, List<P
IRegion[] regions= computeCollapsedRegions(position);
if (regions != null) {
for (IRegion region : regions) {
collapse(region.getOffset(), region.getLength(), fireRedraw);
collapse(region.getOffset(), region.getLength(), fireRedraw, false);
}
}
} else {
expand(position.getOffset(), position.getLength(), fireRedraw);
expand(position.getOffset(), position.getLength(), fireRedraw, false);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion tests/org.eclipse.jface.text.tests/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %Plugin.name
Bundle-SymbolicName: org.eclipse.jface.text.tests
Bundle-Version: 3.13.1000.qualifier
Bundle-Version: 3.13.1100.qualifier
Bundle-Vendor: %Plugin.providerName
Bundle-Localization: plugin
Export-Package:
Expand Down
Loading