diff --git a/bundles/org.eclipse.jface.text/projection/org/eclipse/jface/text/source/projection/ProjectionViewer.java b/bundles/org.eclipse.jface.text/projection/org/eclipse/jface/text/source/projection/ProjectionViewer.java index 576c0212ec9..e0c9e949504 100644 --- a/bundles/org.eclipse.jface.text/projection/org/eclipse/jface/text/source/projection/ProjectionViewer.java +++ b/bundles/org.eclipse.jface.text/projection/org/eclipse/jface/text/source/projection/ProjectionViewer.java @@ -281,23 +281,23 @@ 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. + * An {@link IDocumentListener} that makes sure that {@link #fConfiguredVisibleRegion} 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) { + if (fConfiguredVisibleRegion == 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()); + int oldVisibleRegionEnd= fConfiguredVisibleRegion.getOffset() + fConfiguredVisibleRegion.getLength(); + if (event.getOffset() < fConfiguredVisibleRegion.getOffset()) { + fConfiguredVisibleRegion= new Region(fConfiguredVisibleRegion.getOffset() + newLength - oldLength, fConfiguredVisibleRegion.getLength()); } else if (event.getOffset() + oldLength <= oldVisibleRegionEnd) { - fVisibleRegionDuringProjection= new Region(fVisibleRegionDuringProjection.getOffset(), fVisibleRegionDuringProjection.getLength() + newLength - oldLength); + fConfiguredVisibleRegion= new Region(fConfiguredVisibleRegion.getOffset(), fConfiguredVisibleRegion.getLength() + newLength - oldLength); } } @@ -324,13 +324,11 @@ public void documentAboutToBeChanged(DocumentEvent event) { private List fPendingRequests= new ArrayList<>(); /** The replace-visible-document execution trigger */ private IDocument fReplaceVisibleDocumentExecutionTrigger; - /** true 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 null - * if not in a projection + * The region set by {@link #setVisibleRegion(int, int)} or null + * if no visible region has been set */ - private IRegion fVisibleRegionDuringProjection; + private IRegion fConfiguredVisibleRegion; /** The queue of projection commands used to assess the costs of projection changes. */ private ProjectionCommandQueue fCommandQueue; /** @@ -340,7 +338,7 @@ public void documentAboutToBeChanged(DocumentEvent event) { */ private int fDeletedLines; - private UpdateDocumentListener fUpdateDocumentListener; + private final UpdateDocumentListener fUpdateDocumentListener; /** * Creates a new projection source viewer. @@ -400,12 +398,15 @@ public void setDocument(IDocument document, IAnnotationModel annotationModel, in } if (fProjectionAnnotationModel != null) { - removeDocumentUpdateListener(); wasProjectionEnabled= removeProjectionAnnotationModel(getVisualAnnotationModel()) != null; fProjectionAnnotationModel= null; } + removeDocumentUpdateListener(); super.setDocument(document, annotationModel, modelRangeOffset, modelRangeLength); + if (document != null) { + document.addDocumentListener(fUpdateDocumentListener); + } if (wasProjectionEnabled && document != null) { enableProjection(); @@ -560,9 +561,9 @@ public final void disableProjection() { fProjectionAnnotationModel.removeAllAnnotations(); fFindReplaceDocumentAdapter= null; fireProjectionDisabled(); - if (fVisibleRegionDuringProjection != null) { - super.setVisibleRegion(fVisibleRegionDuringProjection.getOffset(), fVisibleRegionDuringProjection.getLength()); - fVisibleRegionDuringProjection= null; + if (fConfiguredVisibleRegion != null) { + super.setVisibleRegion(fConfiguredVisibleRegion.getOffset(), fConfiguredVisibleRegion.getLength()); + fConfiguredVisibleRegion= null; } removeDocumentUpdateListener(); } @@ -580,8 +581,9 @@ public final void enableProjection() { if (document == null) { return; } - IRegion visibleRegion= getVisibleRegion(); + IRegion visibleRegion= fConfiguredVisibleRegion; if (visibleRegion != null && (visibleRegion.getOffset() != 0 || visibleRegion.getLength() != 0) && visibleRegion.getLength() < document.getLength()) { + fConfiguredVisibleRegion= null; setVisibleRegion(visibleRegion.getOffset(), visibleRegion.getLength()); } document.addDocumentListener(fUpdateDocumentListener); @@ -593,9 +595,9 @@ private void expandAll() { IDocument doc= getDocument(); int length= doc == null ? 0 : doc.getLength(); if (isProjectionMode()) { - if (fVisibleRegionDuringProjection != null) { - offset= fVisibleRegionDuringProjection.getOffset(); - length= fVisibleRegionDuringProjection.getLength(); + if (fConfiguredVisibleRegion != null) { + offset= fConfiguredVisibleRegion.getOffset(); + length= fConfiguredVisibleRegion.getLength(); } fProjectionAnnotationModel.expandAll(offset, length); } @@ -757,6 +759,7 @@ private int toLineStart(IDocument document, int offset, boolean testLastLine) th public void setVisibleRegion(int start, int length) { if (!isProjectionMode()) { super.setVisibleRegion(start, length); + fConfiguredVisibleRegion= new Region(start, length); return; } IDocument document= getDocument(); @@ -769,7 +772,7 @@ public void setVisibleRegion(int start, int length) { int end= computeEndOfVisibleRegion(start, length, document); expandOutsideCurrentVisibleRegion(document); collapseOutsideOfNewVisibleRegion(start, end, document); - fVisibleRegionDuringProjection= new Region(start, end - start - 1); + fConfiguredVisibleRegion= 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)); @@ -777,9 +780,9 @@ public void setVisibleRegion(int start, int length) { } private void expandOutsideCurrentVisibleRegion(IDocument document) throws BadLocationException { - if (fVisibleRegionDuringProjection != null) { - expand(0, fVisibleRegionDuringProjection.getOffset(), false, true); - int oldEnd= fVisibleRegionDuringProjection.getOffset() + fVisibleRegionDuringProjection.getLength(); + if (fConfiguredVisibleRegion != null) { + expand(0, fConfiguredVisibleRegion.getOffset(), false, true); + int oldEnd= fConfiguredVisibleRegion.getOffset() + fConfiguredVisibleRegion.getLength(); int length= document.getLength() - oldEnd; if (length > 0) { expand(oldEnd, length, false, true); @@ -841,16 +844,23 @@ protected void setVisibleDocument(IDocument document) { @Override public void resetVisibleRegion() { - super.resetVisibleRegion(); - if (fWasProjectionEnabled) { - enableProjection(); + if (isProjectionMode()) { + try { + expandOutsideCurrentVisibleRegion(getDocument()); + } catch (BadLocationException e) { + ILog log= ILog.of(getClass()); + log.log(new Status(IStatus.WARNING, getClass(), IStatus.OK, null, e)); + } + } else { + super.resetVisibleRegion(); } + fConfiguredVisibleRegion= null; } @Override public IRegion getVisibleRegion() { - if (fVisibleRegionDuringProjection != null) { - return fVisibleRegionDuringProjection; + if (isProjectionMode() && fConfiguredVisibleRegion != null) { + return fConfiguredVisibleRegion; } IRegion visibleRegion= getModelCoverage(); if (visibleRegion == null) { @@ -862,8 +872,8 @@ public IRegion getVisibleRegion() { @Override public boolean overlapsWithVisibleRegion(int offset, int length) { - if (fVisibleRegionDuringProjection != null) { - return TextUtilities.overlaps(fVisibleRegionDuringProjection, new Region(offset, length)); + if (isProjectionMode() && fConfiguredVisibleRegion != null) { + return TextUtilities.overlaps(fConfiguredVisibleRegion, new Region(offset, length)); } IRegion coverage= getModelCoverage(); if (coverage == null) { @@ -999,9 +1009,14 @@ private void expand(int offset, int length, boolean fireRedraw, boolean performO } } - private boolean overlapsWithNonVisibleRegions(int offset, int length) { - return fVisibleRegionDuringProjection != null - && (offset < fVisibleRegionDuringProjection.getOffset() || offset + length > fVisibleRegionDuringProjection.getOffset() + fVisibleRegionDuringProjection.getLength()); + private boolean overlapsWithNonVisibleRegions(int offset, int length) throws BadLocationException { + if (fConfiguredVisibleRegion == null) { + return false; + } + // ignore overlaps within the same line + int visibleRegionStartLineOffset= getDocument().getLineInformationOfOffset(fConfiguredVisibleRegion.getOffset()).getOffset(); + int regionToCheckEndLineOffset= getDocument().getLineInformationOfOffset(offset + length).getOffset(); + return offset < visibleRegionStartLineOffset || regionToCheckEndLineOffset > fConfiguredVisibleRegion.getOffset() + fConfiguredVisibleRegion.getLength(); } /** @@ -1486,7 +1501,6 @@ private boolean willAutoExpand(Position position, int offset, int length) { @Override protected void handleDispose() { - fWasProjectionEnabled= false; removeDocumentUpdateListener(); super.handleDispose(); } diff --git a/bundles/org.eclipse.text/projection/org/eclipse/jface/text/projection/ProjectionDocument.java b/bundles/org.eclipse.text/projection/org/eclipse/jface/text/projection/ProjectionDocument.java index d9bc3f5d5d3..bcb09d1375d 100644 --- a/bundles/org.eclipse.text/projection/org/eclipse/jface/text/projection/ProjectionDocument.java +++ b/bundles/org.eclipse.text/projection/org/eclipse/jface/text/projection/ProjectionDocument.java @@ -417,6 +417,9 @@ private void internalRemoveMasterDocumentRange(int offsetInMaster, int lengthInM if (fragment.getOffset() == offsetInMaster) { fragment.setOffset(offsetInMaster + lengthInMaster); fragment.setLength(fragment.getLength() - lengthInMaster); + if (fragment.getLength() == 0 && offsetInMaster != 0 && offsetInMaster + lengthInMaster == getMasterDocument().getLength()) { + fragment.setOffset(offsetInMaster); + } } else { // split fragment into three fragments, let position updater remove it diff --git a/tests/org.eclipse.text.tests/projection/org/eclipse/text/tests/ProjectionDocumentTest.java b/tests/org.eclipse.text.tests/projection/org/eclipse/text/tests/ProjectionDocumentTest.java index 5e4bb5649fa..a6a78afdb77 100644 --- a/tests/org.eclipse.text.tests/projection/org/eclipse/text/tests/ProjectionDocumentTest.java +++ b/tests/org.eclipse.text.tests/projection/org/eclipse/text/tests/ProjectionDocumentTest.java @@ -27,6 +27,7 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.junit.jupiter.api.Assertions; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.DefaultLineTracker; @@ -2652,4 +2653,39 @@ public void test29_38() { assertTrue(false); } } + + @Test + public void testFullDocumentRangeRemovedAtOnce() throws BadLocationException { + fSlaveDocument.addMasterDocumentRange(0, fMasterDocument.getLength()); + fSlaveDocument.removeMasterDocumentRange(0, fMasterDocument.getLength()); + Assertions.assertEquals("", fSlaveDocument.get()); + Position[] fragments = fSlaveDocument.getFragments2(); + Assertions.assertEquals(1, fragments.length); + Assertions.assertEquals(new Position(fMasterDocument.getLength(), 0), fragments[0]); + } + + @Test + public void testFullDocumentRangeRemovedInTwoParts() throws BadLocationException { + fSlaveDocument.addMasterDocumentRange(0, fMasterDocument.getLength()); + fSlaveDocument.removeMasterDocumentRange(0, 10); + Assertions.assertEquals(fMasterDocument.getLength() - 10, fSlaveDocument.getLength()); + fSlaveDocument.removeMasterDocumentRange(10, fMasterDocument.getLength() - 10); + Assertions.assertEquals("", fSlaveDocument.get()); + Position[] fragments = fSlaveDocument.getFragments2(); + Assertions.assertEquals(1, fragments.length); + Assertions.assertEquals(new Position(10, 0), fragments[0]); + } + + @Test + public void testRemovePartsOfDocument() throws BadLocationException { + fSlaveDocument.addMasterDocumentRange(0, fMasterDocument.getLength()); + fSlaveDocument.removeMasterDocumentRange(0, 10); + Assertions.assertEquals(fMasterDocument.getLength() - 10, fSlaveDocument.getLength()); + fSlaveDocument.removeMasterDocumentRange(10, fMasterDocument.getLength() - 20); + Assertions.assertEquals(fMasterDocument.get(fMasterDocument.getLength() - 10, 10), + fSlaveDocument.get()); + Position[] fragments = fSlaveDocument.getFragments2(); + Assertions.assertEquals(1, fragments.length); + Assertions.assertEquals(new Position(fMasterDocument.getLength() - 10, 10), fragments[0]); + } }