Skip to content

Commit c7d5ff6

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 a988d08 commit c7d5ff6

File tree

4 files changed

+227
-3
lines changed

4 files changed

+227
-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 (bordersOrSurroundsRegion(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 bordersOrSurroundsRegion(Position position, Region region) throws BadLocationException {
1062+
if (atStartOfLine(position.getOffset()) <= region.getOffset() + region.getLength()
1063+
&& atStartOfLine(position.getOffset() + position.length) >= region.getOffset() + region.getLength()) {
1064+
return true;
1065+
}
1066+
return atStartOfLine(position.getOffset()) <= region.getOffset()
1067+
&& position.getOffset() + position.getLength() > atStartOfLine(region.getOffset());
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: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@
1010
*******************************************************************************/
1111
package org.eclipse.jface.text.tests;
1212

13+
import static org.junit.Assert.assertTrue;
1314
import static org.junit.jupiter.api.Assertions.assertEquals;
15+
import static org.junit.jupiter.api.Assertions.assertFalse;
16+
import static org.junit.jupiter.api.Assertions.assertThrows;
1417

1518
import org.junit.jupiter.api.Test;
1619

@@ -19,6 +22,7 @@
1922
import org.eclipse.swt.dnd.TextTransfer;
2023
import org.eclipse.swt.layout.FillLayout;
2124
import org.eclipse.swt.widgets.Composite;
25+
import org.eclipse.swt.widgets.Display;
2226
import org.eclipse.swt.widgets.Shell;
2327

2428
import org.eclipse.jface.text.BadLocationException;
@@ -368,4 +372,148 @@ public void testRemoveEntireVisibleRegion() throws BadLocationException {
368372
shell.dispose();
369373
}
370374
}
375+
376+
@Test
377+
public void testSetVisibleRegionDoesNotExpandOutsideProjectionRegions() {
378+
Shell shell= new Shell();
379+
shell.setLayout(new FillLayout());
380+
TestProjectionViewer viewer= new TestProjectionViewer(shell, null, null, false, SWT.NONE);
381+
String documentContent= """
382+
Hello
383+
World
384+
abc
385+
123
386+
456
387+
789
388+
""";
389+
Document document= new Document(documentContent);
390+
viewer.setDocument(document, new AnnotationModel());
391+
viewer.enableProjection();
392+
ProjectionAnnotation firstAnnotation= new ProjectionAnnotation(true);
393+
ProjectionAnnotation secondAnnotation= new ProjectionAnnotation(true);
394+
viewer.getProjectionAnnotationModel().addAnnotation(firstAnnotation, new Position(0, documentContent.indexOf("World")));
395+
viewer.getProjectionAnnotationModel().addAnnotation(secondAnnotation, new Position(documentContent.indexOf("456"), documentContent.length() - documentContent.indexOf("456")));
396+
397+
viewer.setVisibleRegion(documentContent.indexOf("abc"), documentContent.indexOf("123") - documentContent.indexOf("abc"));
398+
shell.setVisible(true);
399+
try {
400+
assertTrue(firstAnnotation.isCollapsed());
401+
assertTrue(secondAnnotation.isCollapsed());
402+
} finally {
403+
shell.dispose();
404+
}
405+
}
406+
407+
@Test
408+
public void testSetVisibleRegionExpandsBorderingProjectionRegions() {
409+
Shell shell= new Shell();
410+
shell.setLayout(new FillLayout());
411+
TestProjectionViewer viewer= new TestProjectionViewer(shell, null, null, false, SWT.NONE);
412+
String documentContent= """
413+
Hello
414+
World
415+
123
416+
456
417+
""";
418+
Document document= new Document(documentContent);
419+
viewer.setDocument(document, new AnnotationModel());
420+
viewer.enableProjection();
421+
ProjectionAnnotation firstAnnotation= new ProjectionAnnotation(true);
422+
ProjectionAnnotation secondAnnotation= new ProjectionAnnotation(true);
423+
viewer.getProjectionAnnotationModel().addAnnotation(firstAnnotation, new Position(0, documentContent.indexOf("123")));
424+
viewer.getProjectionAnnotationModel().addAnnotation(secondAnnotation, new Position(documentContent.indexOf("123"), documentContent.length() - documentContent.indexOf("123")));
425+
426+
viewer.setVisibleRegion(documentContent.indexOf("World"), documentContent.indexOf("456") - documentContent.indexOf("World"));
427+
shell.setVisible(true);
428+
try {
429+
assertFalse(firstAnnotation.isCollapsed());
430+
assertFalse(secondAnnotation.isCollapsed());
431+
} finally {
432+
shell.dispose();
433+
}
434+
}
435+
436+
@Test
437+
public void testProjectionRegionsShownOnlyInVisibleRegion() {
438+
Shell shell= new Shell(Display.getCurrent());
439+
shell.setLayout(new FillLayout());
440+
TestProjectionViewer viewer= new TestProjectionViewer(shell, null, null, true, SWT.ALL);
441+
String documentContent= """
442+
443+
visible_region_start
444+
445+
projection_start
446+
447+
visible_region_end
448+
449+
projection_end
450+
451+
""";
452+
Document document= new Document(documentContent);
453+
viewer.setDocument(document, new AnnotationModel());
454+
ProjectionAnnotation annotation= addVisibleRegionAndProjection(viewer, documentContent);
455+
try {
456+
assertEquals("""
457+
visible_region_start
458+
459+
projection_start
460+
461+
visible_region_end
462+
""", viewer.getVisibleDocument().get());
463+
464+
annotation.paint(null, null, null); //should exit early and not throw NPE
465+
} finally {
466+
shell.dispose();
467+
}
468+
}
469+
470+
@Test
471+
public void testProjectionRegionsShownWithinVisibleRegion() {
472+
Shell shell= new Shell(Display.getCurrent());
473+
shell.setLayout(new FillLayout());
474+
TestProjectionViewer viewer= new TestProjectionViewer(shell, null, null, true, SWT.ALL);
475+
String documentContent= """
476+
477+
visible_region_start
478+
479+
projection_start
480+
481+
projection_end
482+
483+
visible_region_end
484+
485+
""";
486+
Document document= new Document(documentContent);
487+
viewer.setDocument(document, new AnnotationModel());
488+
ProjectionAnnotation annotation= addVisibleRegionAndProjection(viewer, documentContent);
489+
try {
490+
assertEquals("""
491+
visible_region_start
492+
493+
projection_start
494+
495+
projection_end
496+
497+
visible_region_end
498+
""", viewer.getVisibleDocument().get());
499+
500+
assertThrows(NullPointerException.class, () -> annotation.paint(null, null, null), "expected to run painting logic");
501+
} finally {
502+
shell.dispose();
503+
}
504+
}
505+
506+
private ProjectionAnnotation addVisibleRegionAndProjection(TestProjectionViewer viewer, String documentContent) {
507+
int visibleRegionStart= documentContent.indexOf("visible_region_start");
508+
int visibleRegionEnd= documentContent.indexOf("\n", documentContent.indexOf("visible_region_end")) + 1;
509+
510+
int projectionStart= documentContent.indexOf("projection_start");
511+
int projectionEnd= documentContent.indexOf("\n", documentContent.indexOf("projection_end")) + 1;
512+
513+
viewer.setVisibleRegion(visibleRegionStart, visibleRegionEnd - visibleRegionStart);
514+
viewer.enableProjection();
515+
ProjectionAnnotation annotation= new ProjectionAnnotation();
516+
viewer.getProjectionAnnotationModel().addAnnotation(annotation, new Position(projectionStart, projectionEnd - projectionStart));
517+
return annotation;
518+
}
371519
}

0 commit comments

Comments
 (0)