Skip to content

Commit 67a1490

Browse files
committed
hide projection annotations outside of the current visible region
Projection regions that overlap with parts of the file outside of the current visible region cannot be collapsed hence their annotations should not be painted.
1 parent 937cbb2 commit 67a1490

File tree

4 files changed

+165
-3
lines changed

4 files changed

+165
-3
lines changed

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ public void run() {
7373
/** Indicates whether this annotation should be painted as range */
7474
private boolean fIsRangeIndication= false;
7575

76+
private boolean hidden= false;
77+
7678
/**
7779
* Creates a new expanded projection annotation.
7880
*/
@@ -115,6 +117,9 @@ private void drawRangeIndication(GC gc, Canvas canvas, Rectangle r) {
115117

116118
@Override
117119
public void paint(GC gc, Canvas canvas, Rectangle rectangle) {
120+
if (hidden) {
121+
return;
122+
}
118123
Image image= getImage(canvas.getDisplay());
119124
if (image != null) {
120125
ImageUtilities.drawImage(image, gc, canvas, rectangle, SWT.CENTER, SWT.TOP);
@@ -128,6 +133,10 @@ public void paint(GC gc, Canvas canvas, Rectangle rectangle) {
128133
}
129134
}
130135

136+
void setHidden(boolean hidden) {
137+
this.hidden= hidden;
138+
}
139+
131140
@Override
132141
public int getLayer() {
133142
return IAnnotationPresentation.DEFAULT_LAYER;

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

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ private void processModelChanged(IAnnotationModel model, AnnotationModelEvent ev
124124
fProjectionSummary.updateSummaries();
125125
}
126126
processCatchupRequest(event);
127+
correctChangedAnnotationVisibility(event);
127128

128129
} else if (model == getAnnotationModel() && fProjectionSummary != null) {
129130
fProjectionSummary.updateSummaries();
@@ -770,15 +771,46 @@ public void setVisibleRegion(int start, int length) {
770771
// If the visible region changes, make sure collapsed regions outside of the old visible regions are expanded
771772
// and collapse everything outside the new visible region
772773
int end= computeEndOfVisibleRegion(start, length, document);
774+
Region newVisibleRegion= new Region(start, end - start - 1);
775+
expandProjectionAnnotationsBorderingRegion(newVisibleRegion);
773776
expandOutsideCurrentVisibleRegion(document);
774777
collapseOutsideOfNewVisibleRegion(start, end, document);
775-
fConfiguredVisibleRegion= new Region(start, end - start - 1);
778+
fConfiguredVisibleRegion= newVisibleRegion;
779+
hideProjectionAnnotationsOutsideOfVisibleRegion();
776780
} catch (BadLocationException e) {
777781
ILog log= ILog.of(getClass());
778782
log.log(new Status(IStatus.WARNING, getClass(), IStatus.OK, null, e));
779783
}
780784
}
781785

786+
private void expandProjectionAnnotationsBorderingRegion(Region region) throws BadLocationException {
787+
for (Iterator<Annotation> it= fProjectionAnnotationModel.getAnnotationIterator(); it.hasNext();) {
788+
Annotation annotation= it.next();
789+
Position position= fProjectionAnnotationModel.getPosition(annotation);
790+
if (bordersRegion(position, region)) {
791+
fProjectionAnnotationModel.expand(annotation);
792+
}
793+
}
794+
}
795+
796+
private void hideProjectionAnnotationsOutsideOfVisibleRegion() throws BadLocationException {
797+
for (Iterator<Annotation> it= fProjectionAnnotationModel.getAnnotationIterator(); it.hasNext();) {
798+
Annotation annotation= it.next();
799+
hideProjectionAnnotationIfPartsAreOutsideOfVisibleRegion(annotation);
800+
}
801+
}
802+
803+
private void hideProjectionAnnotationIfPartsAreOutsideOfVisibleRegion(Annotation annotation) throws BadLocationException {
804+
Position position= fProjectionAnnotationModel.getPosition(annotation);
805+
if (annotation instanceof ProjectionAnnotation a) {
806+
if (overlapsWithNonVisibleRegions(position.getOffset(), position.getLength())) {
807+
a.setHidden(true);
808+
} else {
809+
a.setHidden(false);
810+
}
811+
}
812+
}
813+
782814
private void expandOutsideCurrentVisibleRegion(IDocument document) throws BadLocationException {
783815
if (fConfiguredVisibleRegion != null) {
784816
expand(0, fConfiguredVisibleRegion.getOffset(), false, true);
@@ -855,6 +887,12 @@ public void resetVisibleRegion() {
855887
super.resetVisibleRegion();
856888
}
857889
fConfiguredVisibleRegion= null;
890+
for (Iterator<Annotation> it= fProjectionAnnotationModel.getAnnotationIterator(); it.hasNext();) {
891+
Annotation annotation= it.next();
892+
if (annotation instanceof ProjectionAnnotation a) {
893+
a.setHidden(false);
894+
}
895+
}
858896
}
859897

860898
@Override
@@ -1014,11 +1052,25 @@ private boolean overlapsWithNonVisibleRegions(int offset, int length) throws Bad
10141052
return false;
10151053
}
10161054
// ignore overlaps within the same line
1017-
int visibleRegionStartLineOffset= getDocument().getLineInformationOfOffset(fConfiguredVisibleRegion.getOffset()).getOffset();
1018-
int regionToCheckEndLineOffset= getDocument().getLineInformationOfOffset(offset + length).getOffset();
1055+
int visibleRegionStartLineOffset= atStartOfLine(fConfiguredVisibleRegion.getOffset());
1056+
int regionToCheckEndLineOffset= atStartOfLine(offset + length);
10191057
return offset < visibleRegionStartLineOffset || regionToCheckEndLineOffset > fConfiguredVisibleRegion.getOffset() + fConfiguredVisibleRegion.getLength();
10201058
}
10211059

1060+
1061+
private boolean bordersRegion(Position position, Region region) throws BadLocationException {
1062+
if (position.getOffset() > atStartOfLine(region.getOffset())
1063+
&& atStartOfLine(position.getOffset() + position.getLength()) < region.getOffset() + region.getLength()) {
1064+
return true;
1065+
}
1066+
return atStartOfLine(position.getOffset()) < region.getOffset()
1067+
&& position.getOffset() + position.getLength() > atStartOfLine(region.getOffset() + region.getLength());
1068+
}
1069+
1070+
private int atStartOfLine(int off) throws BadLocationException {
1071+
return getDocument().getLineInformationOfOffset(off).getOffset();
1072+
}
1073+
10221074
/**
10231075
* Processes the request for catch up with the annotation model in the UI thread. If the current
10241076
* thread is not the UI thread or there are pending catch up requests, a new request is posted.
@@ -1090,6 +1142,20 @@ protected final void postCatchupRequest(final AnnotationModelEvent event) {
10901142
}
10911143
}
10921144

1145+
private void correctChangedAnnotationVisibility(AnnotationModelEvent event) {
1146+
try {
1147+
for (Annotation annotation : event.getAddedAnnotations()) {
1148+
hideProjectionAnnotationIfPartsAreOutsideOfVisibleRegion(annotation);
1149+
}
1150+
for (Annotation annotation : event.getChangedAnnotations()) {
1151+
hideProjectionAnnotationIfPartsAreOutsideOfVisibleRegion(annotation);
1152+
}
1153+
} catch (BadLocationException e) {
1154+
ILog log= ILog.of(getClass());
1155+
log.log(new Status(IStatus.WARNING, getClass(), IStatus.OK, null, e));
1156+
}
1157+
}
1158+
10931159
/**
10941160
* Tests whether the visible document's master document
10951161
* is identical to this viewer's document.

tests/org.eclipse.jface.text.tests/META-INF/MANIFEST.MF

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ Import-Package: org.mockito,
3030
org.mockito.invocation,
3131
org.mockito.stubbing,
3232
org.junit.jupiter.api;version="[5.14.0,6.0.0)",
33+
org.junit.jupiter.api.function;version="[5.14.0,6.0.0)",
3334
org.junit.jupiter.params;version="[5.14.0,6.0.0)",
3435
org.junit.jupiter.params.provider;version="[5.14.0,6.0.0)",
3536
org.junit.platform.suite.api;version="[1.14.0,2.0.0)"

tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/ProjectionViewerTest.java

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
package org.eclipse.jface.text.tests;
1212

1313
import static org.junit.jupiter.api.Assertions.assertEquals;
14+
import static org.junit.jupiter.api.Assertions.assertThrows;
1415

1516
import org.junit.jupiter.api.Test;
1617

@@ -19,6 +20,7 @@
1920
import org.eclipse.swt.dnd.TextTransfer;
2021
import org.eclipse.swt.layout.FillLayout;
2122
import org.eclipse.swt.widgets.Composite;
23+
import org.eclipse.swt.widgets.Display;
2224
import org.eclipse.swt.widgets.Shell;
2325

2426
import org.eclipse.jface.text.BadLocationException;
@@ -368,4 +370,88 @@ public void testRemoveEntireVisibleRegion() throws BadLocationException {
368370
shell.dispose();
369371
}
370372
}
373+
374+
@Test
375+
public void testProjectionRegionsShownOnlyInVisibleRegion() {
376+
Shell shell= new Shell(Display.getCurrent());
377+
shell.setLayout(new FillLayout());
378+
TestProjectionViewer viewer= new TestProjectionViewer(shell, null, null, true, SWT.ALL);
379+
String documentContent= """
380+
381+
visible_region_start
382+
383+
projection_start
384+
385+
visible_region_end
386+
387+
projection_end
388+
389+
""";
390+
Document document= new Document(documentContent);
391+
viewer.setDocument(document, new AnnotationModel());
392+
ProjectionAnnotation annotation= addVisibleRegionAndProjection(viewer, documentContent);
393+
try {
394+
assertEquals("""
395+
visible_region_start
396+
397+
projection_start
398+
399+
visible_region_end
400+
""", viewer.getVisibleDocument().get());
401+
402+
annotation.paint(null, null, null); //should exit early and not throw NPE
403+
} finally {
404+
shell.dispose();
405+
}
406+
}
407+
408+
@Test
409+
public void testProjectionRegionsShownWithinVisibleRegion() {
410+
Shell shell= new Shell(Display.getCurrent());
411+
shell.setLayout(new FillLayout());
412+
TestProjectionViewer viewer= new TestProjectionViewer(shell, null, null, true, SWT.ALL);
413+
String documentContent= """
414+
415+
visible_region_start
416+
417+
projection_start
418+
419+
projection_end
420+
421+
visible_region_end
422+
423+
""";
424+
Document document= new Document(documentContent);
425+
viewer.setDocument(document, new AnnotationModel());
426+
ProjectionAnnotation annotation= addVisibleRegionAndProjection(viewer, documentContent);
427+
try {
428+
assertEquals("""
429+
visible_region_start
430+
431+
projection_start
432+
433+
projection_end
434+
435+
visible_region_end
436+
""", viewer.getVisibleDocument().get());
437+
438+
assertThrows(NullPointerException.class, () -> annotation.paint(null, null, null), "expected to run painting logic");
439+
} finally {
440+
shell.dispose();
441+
}
442+
}
443+
444+
private ProjectionAnnotation addVisibleRegionAndProjection(TestProjectionViewer viewer, String documentContent) {
445+
int visibleRegionStart= documentContent.indexOf("visible_region_start");
446+
int visibleRegionEnd= documentContent.indexOf("\n", documentContent.indexOf("visible_region_end")) + 1;
447+
448+
int projectionStart= documentContent.indexOf("projection_start");
449+
int projectionEnd= documentContent.indexOf("\n", documentContent.indexOf("projection_end")) + 1;
450+
451+
viewer.setVisibleRegion(visibleRegionStart, visibleRegionEnd - visibleRegionStart);
452+
viewer.enableProjection();
453+
ProjectionAnnotation annotation= new ProjectionAnnotation();
454+
viewer.getProjectionAnnotationModel().addAnnotation(annotation, new Position(projectionStart, projectionEnd - projectionStart));
455+
return annotation;
456+
}
371457
}

0 commit comments

Comments
 (0)