Skip to content

Commit 17cd4dd

Browse files
committed
Prevent IllegalStateException setting visible region with collapsed
projection region
1 parent 3a441d9 commit 17cd4dd

File tree

2 files changed

+143
-1
lines changed

2 files changed

+143
-1
lines changed

bundles/org.eclipse.text/projection/org/eclipse/jface/text/projection/ProjectionDocument.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
package org.eclipse.jface.text.projection;
1616

1717
import java.util.ArrayList;
18+
import java.util.Arrays;
19+
import java.util.Comparator;
1820
import java.util.List;
1921

2022
import org.eclipse.jface.text.AbstractDocument;
@@ -345,7 +347,9 @@ private void internalAddMasterDocumentRange(int offsetInMaster, int lengthInMast
345347
int endOffset= offsetInMaster +lengthInMaster;
346348
left.setLength(endOffset - left.getOffset());
347349
left.segment.markForStretch();
348-
350+
if (index < fragments.length && fragments[index].getOffset() + fragments[index].getLength() < left.getOffset() + left.getLength()) {
351+
((Fragment)fragments[index]).segment.delete();
352+
}
349353
} else if (right != null) {
350354
right.setOffset(right.getOffset() - lengthInMaster);
351355
right.setLength(right.getLength() + lengthInMaster);
@@ -438,6 +442,11 @@ private void internalRemoveMasterDocumentRange(int offsetInMaster, int lengthInM
438442
segment= new Segment(offset, fragment.segment.getOffset() + fragment.segment.getLength() - offset);
439443
newFragment.segment= segment;
440444
segment.fragment= newFragment;
445+
if (newFragment.getLength() != 0 && fMasterDocument.containsPosition(fFragmentsCategory, newFragment.getOffset(), 0)) {
446+
// prevent inserting position with non-zero length after position with 0-length by removing zero-length position
447+
removePositionAt(fMasterDocument, fFragmentsCategory, new Position(newFragment.getOffset(), 0));
448+
removePositionAt(this, fSegmentsCategory, new Position(fMapping.toImageOffset(newFragment.getOffset()), 0));
449+
}
441450
fMasterDocument.addPosition(fFragmentsCategory, newFragment);
442451
addPosition(fSegmentsCategory, segment);
443452

@@ -454,6 +463,14 @@ private void internalRemoveMasterDocumentRange(int offsetInMaster, int lengthInM
454463
}
455464
}
456465

466+
private void removePositionAt(IDocument document, String category, Position toRemove) throws BadPositionCategoryException {
467+
Position[] positions= document.getPositions(category);
468+
int index= Arrays.binarySearch(positions, toRemove, Comparator.comparingInt(Position::getOffset));
469+
if (index != -1) {
470+
document.removePosition(category, positions[index]);
471+
}
472+
}
473+
457474
/**
458475
* Returns the sequence of all master document regions which are contained
459476
* in the given master document range and which are not yet part of this

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

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,13 @@
2828
import org.eclipse.jface.text.BadLocationException;
2929
import org.eclipse.jface.text.Document;
3030
import org.eclipse.jface.text.IDocument;
31+
import org.eclipse.jface.text.IDocumentInformationMapping;
3132
import org.eclipse.jface.text.IRegion;
3233
import org.eclipse.jface.text.ITextOperationTarget;
3334
import org.eclipse.jface.text.ITextSelection;
3435
import org.eclipse.jface.text.Position;
3536
import org.eclipse.jface.text.Region;
37+
import org.eclipse.jface.text.projection.ProjectionDocument;
3638
import org.eclipse.jface.text.source.AnnotationModel;
3739
import org.eclipse.jface.text.source.IOverviewRuler;
3840
import org.eclipse.jface.text.source.IVerticalRuler;
@@ -433,6 +435,129 @@ public void testSetVisibleRegionExpandsBorderingProjectionRegions() {
433435
}
434436
}
435437

438+
@Test
439+
public void testImageLineStateAfterSettingVisibleRegionsWithProjectionsSetMethodAndClass() throws BadLocationException {
440+
// https://github.com/eclipse-platform/eclipse.platform.ui/pull/3456
441+
Shell shell= new Shell();
442+
shell.setLayout(new FillLayout());
443+
TestProjectionViewer viewer= new TestProjectionViewer(shell, null, null, false, SWT.NONE);
444+
String documentContent= """
445+
public class TM {
446+
void a() {
447+
// ...
448+
}
449+
450+
void b() {
451+
// ...
452+
}
453+
454+
void c() {
455+
// ...
456+
}
457+
}
458+
""";
459+
Document document= new Document(documentContent);
460+
viewer.setDocument(document, new AnnotationModel());
461+
viewer.enableProjection();
462+
addAnnotationBetween(viewer, new ProjectionAnnotation(false), "\tvoid a()", "\t}");
463+
ProjectionAnnotation annotationToCollapse= new ProjectionAnnotation(false);
464+
addAnnotationBetween(viewer, annotationToCollapse, "\tvoid b()", "\t}");
465+
addAnnotationBetween(viewer, new ProjectionAnnotation(false), "\tvoid c()", "\t}");
466+
467+
shell.setVisible(true);
468+
try {
469+
viewer.getProjectionAnnotationModel().collapse(annotationToCollapse);
470+
471+
Position firstMethod= findPositionFromStartAndEndText(viewer, "\tvoid a()", "}");
472+
viewer.setVisibleRegion(firstMethod.getOffset(), firstMethod.getLength() + 1);
473+
viewer.setVisibleRegion(documentContent.indexOf("class"), documentContent.length() - documentContent.indexOf("class"));
474+
475+
IDocumentInformationMapping mapping= ((ProjectionDocument) viewer.getVisibleDocument()).getDocumentInformationMapping();
476+
// toImageLine should not throw exceptions and yield the correct values
477+
for (int i= 0; i < 5; i++) {
478+
int imageLine= mapping.toImageLine(i);// should not throw exception
479+
assertEquals(i, imageLine);
480+
}
481+
assertEquals(-1, mapping.toImageLine(6), "should still be collapsed");
482+
for (int i= 7; i < documentContent.split("\n").length; i++) {
483+
int imageLine= mapping.toImageLine(i);// should not throw exception
484+
assertEquals(i - 1, imageLine);
485+
}
486+
} finally {
487+
shell.dispose();
488+
}
489+
}
490+
491+
@Test
492+
public void testImageLineStateAfterSettingVisibleRegionsWithProjectionsSetDifferentMethods() throws BadLocationException {
493+
// https://github.com/eclipse-platform/eclipse.platform.ui/pull/3456
494+
Shell shell= new Shell();
495+
shell.setLayout(new FillLayout());
496+
TestProjectionViewer viewer= new TestProjectionViewer(shell, null, null, false, SWT.NONE);
497+
String documentContent= """
498+
public class TM {
499+
void a() {
500+
// ...
501+
}
502+
503+
void b() {
504+
// ...
505+
}
506+
507+
void c() {
508+
// ...
509+
}
510+
}
511+
""";
512+
Document document= new Document(documentContent);
513+
viewer.setDocument(document, new AnnotationModel());
514+
viewer.enableProjection();
515+
addAnnotationBetween(viewer, new ProjectionAnnotation(false), "\tvoid a()", "\t}");
516+
ProjectionAnnotation annotationToCollapse= new ProjectionAnnotation(false);
517+
addAnnotationBetween(viewer, annotationToCollapse, "\tvoid b()", "\t}");
518+
addAnnotationBetween(viewer, new ProjectionAnnotation(false), "\tvoid c()", "\t}");
519+
520+
shell.setVisible(true);
521+
try {
522+
viewer.getProjectionAnnotationModel().collapse(annotationToCollapse);
523+
524+
Position firstMethod= findPositionFromStartAndEndText(viewer, "\tvoid a()", "}");
525+
viewer.setVisibleRegion(firstMethod.getOffset(), firstMethod.getLength() + 1);
526+
Position secondMethod= findPositionFromStartAndEndText(viewer, "\tvoid b()", "}");
527+
viewer.setVisibleRegion(secondMethod.getOffset(), secondMethod.getLength());
528+
529+
assertEquals("""
530+
void b() {
531+
// ...
532+
""", viewer.getVisibleDocument().get());
533+
534+
IDocumentInformationMapping mapping= ((ProjectionDocument) viewer.getVisibleDocument()).getDocumentInformationMapping();
535+
536+
assertEquals(0, mapping.toImageLine(5));
537+
assertEquals(1, mapping.toImageLine(6));
538+
for (int i= 0; i < documentContent.split("\n").length; i++) {
539+
if (i < 5 || i > 6) {
540+
assertEquals(-1, mapping.toImageLine(i));
541+
}
542+
}
543+
} finally {
544+
shell.dispose();
545+
}
546+
}
547+
548+
private void addAnnotationBetween(TestProjectionViewer viewer, ProjectionAnnotation annotationToCollapse, String startText, String endText) {
549+
Position position= findPositionFromStartAndEndText(viewer, startText, endText);
550+
viewer.getProjectionAnnotationModel().addAnnotation(annotationToCollapse, position);
551+
}
552+
553+
private Position findPositionFromStartAndEndText(TestProjectionViewer viewer, String startText, String endText) {
554+
String documentContent= viewer.getDocument().get();
555+
int startIndex= documentContent.indexOf(startText);
556+
int endIndex= documentContent.indexOf(endText, startIndex + 1);
557+
Position position= new Position(startIndex, endIndex - startIndex);
558+
return position;
559+
}
560+
436561
@Test
437562
public void testProjectionRegionsShownOnlyInVisibleRegion() {
438563
Shell shell= new Shell(Display.getCurrent());

0 commit comments

Comments
 (0)