Skip to content

Commit 0ba0f28

Browse files
danthe1stvogella
authored andcommitted
Prevent IllegalStateException setting visible region with collapsed
projection region
1 parent 60ee73f commit 0ba0f28

File tree

2 files changed

+148
-2
lines changed

2 files changed

+148
-2
lines changed

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

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2000, 2010 IBM Corporation and others.
2+
* Copyright (c) 2000, 2026 IBM Corporation and others.
33
*
44
* This program and the accompanying materials
55
* are made available under the terms of the Eclipse Public License 2.0
@@ -15,6 +15,7 @@
1515
package org.eclipse.jface.text.projection;
1616

1717
import java.util.ArrayList;
18+
import java.util.Arrays;
1819
import java.util.List;
1920

2021
import org.eclipse.jface.text.AbstractDocument;
@@ -345,7 +346,9 @@ private void internalAddMasterDocumentRange(int offsetInMaster, int lengthInMast
345346
int endOffset= offsetInMaster +lengthInMaster;
346347
left.setLength(endOffset - left.getOffset());
347348
left.segment.markForStretch();
348-
349+
if (index < fragments.length && fragments[index].getOffset() + fragments[index].getLength() < left.getOffset() + left.getLength()) {
350+
((Fragment)fragments[index]).segment.delete();
351+
}
349352
} else if (right != null) {
350353
right.setOffset(right.getOffset() - lengthInMaster);
351354
right.setLength(right.getLength() + lengthInMaster);
@@ -438,6 +441,11 @@ private void internalRemoveMasterDocumentRange(int offsetInMaster, int lengthInM
438441
segment= new Segment(offset, fragment.segment.getOffset() + fragment.segment.getLength() - offset);
439442
newFragment.segment= segment;
440443
segment.fragment= newFragment;
444+
if (newFragment.getLength() != 0 && fMasterDocument.containsPosition(fFragmentsCategory, newFragment.getOffset(), 0)) {
445+
// prevent inserting position with non-zero length after position with 0-length by removing zero-length position
446+
removePositionAt(fMasterDocument, fFragmentsCategory, new Position(newFragment.getOffset(), 0));
447+
removePositionAt(this, fSegmentsCategory, new Position(fMapping.toImageOffset(newFragment.getOffset()), 0));
448+
}
441449
fMasterDocument.addPosition(fFragmentsCategory, newFragment);
442450
addPosition(fSegmentsCategory, segment);
443451

@@ -454,6 +462,14 @@ private void internalRemoveMasterDocumentRange(int offsetInMaster, int lengthInM
454462
}
455463
}
456464

465+
private void removePositionAt(IDocument document, String category, Position toRemove) throws BadPositionCategoryException {
466+
Position[] positions= document.getPositions(category);
467+
int index= super.computeIndexInPositionList(Arrays.asList(positions), toRemove.getOffset(), true);
468+
if (index != -1 && positions[index].getLength() == toRemove.getLength()) {
469+
document.removePosition(category, positions[index]);
470+
}
471+
}
472+
457473
/**
458474
* Returns the sequence of all master document regions which are contained
459475
* 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: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,13 @@
3030
import org.eclipse.jface.text.BadLocationException;
3131
import org.eclipse.jface.text.Document;
3232
import org.eclipse.jface.text.IDocument;
33+
import org.eclipse.jface.text.IDocumentInformationMapping;
3334
import org.eclipse.jface.text.IRegion;
3435
import org.eclipse.jface.text.ITextOperationTarget;
3536
import org.eclipse.jface.text.ITextSelection;
3637
import org.eclipse.jface.text.Position;
3738
import org.eclipse.jface.text.Region;
39+
import org.eclipse.jface.text.projection.ProjectionDocument;
3840
import org.eclipse.jface.text.source.AnnotationModel;
3941
import org.eclipse.jface.text.source.IOverviewRuler;
4042
import org.eclipse.jface.text.source.IVerticalRuler;
@@ -435,6 +437,134 @@ public void testSetVisibleRegionExpandsBorderingProjectionRegions() {
435437
}
436438
}
437439

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

0 commit comments

Comments
 (0)