Skip to content

Allow using visible regions with projections #3074

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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,70 @@ 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);
int oldEnd= fVisibleRegionDuringProjection.getOffset() + fVisibleRegionDuringProjection.getLength();
int length= document.getLength() - oldEnd;
if (length > 0) {
expand(oldEnd, length, false);
}
}
}

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

int endInvisibleRegionLength= documentLength - end;
if (endInvisibleRegionLength > 0) {
collapse(end, endInvisibleRegionLength, 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 +829,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 Down
Loading