Skip to content

Commit 2ce3ba0

Browse files
committed
Allow using visible regions with projections #3073
While ProjectionViewer supports both using visible regions and projections, these features cannot be used in conjunction. This change allows the use of projections when visible regions are used. Fixes #3074
1 parent d2177cf commit 2ce3ba0

File tree

2 files changed

+247
-3
lines changed

2 files changed

+247
-3
lines changed

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

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ public class ProjectionViewer extends SourceViewer implements ITextViewerExtensi
9999
*/
100100
public static final int COLLAPSE_ALL= BASE + 5;
101101

102+
102103
/**
103104
* Internal listener to changes of the annotation model.
104105
*/
@@ -272,6 +273,34 @@ private void computeExpectedExecutionCosts() {
272273
}
273274
}
274275

276+
/**
277+
* An {@link IDocumentListener} that makes sure that {@link #fVisibleRegionDuringProjection} is
278+
* updated when the document changes and ensures that the collapsed region after the visible
279+
* region is recreated appropriately.
280+
*/
281+
private final class UpdateDocumentListener implements IDocumentListener {
282+
@Override
283+
public void documentChanged(DocumentEvent event) {
284+
if (fVisibleRegionDuringProjection != null) {
285+
int oldLength= event.getLength();
286+
int newLength= event.getText().length();
287+
int oldVisibleRegionEnd= fVisibleRegionDuringProjection.getOffset() + fVisibleRegionDuringProjection.getLength();
288+
289+
if (event.getOffset() < fVisibleRegionDuringProjection.getOffset()) {
290+
fVisibleRegionDuringProjection= new Region(fVisibleRegionDuringProjection.getOffset() + newLength - oldLength, fVisibleRegionDuringProjection.getLength());
291+
} else {
292+
if (event.getOffset() + oldLength < oldVisibleRegionEnd) {
293+
fVisibleRegionDuringProjection= new Region(fVisibleRegionDuringProjection.getOffset(), fVisibleRegionDuringProjection.getLength() + newLength - oldLength);
294+
}
295+
}
296+
}
297+
}
298+
299+
@Override
300+
public void documentAboutToBeChanged(DocumentEvent event) {
301+
}
302+
}
303+
275304
/** The projection annotation model used by this viewer. */
276305
private ProjectionAnnotationModel fProjectionAnnotationModel;
277306
/** The annotation model listener */
@@ -292,6 +321,11 @@ private void computeExpectedExecutionCosts() {
292321
private IDocument fReplaceVisibleDocumentExecutionTrigger;
293322
/** <code>true</code> if projection was on the last time we switched to segmented mode. */
294323
private boolean fWasProjectionEnabled;
324+
/**
325+
* The region set by {@link #setVisibleRegion(int, int)} during projection or <code>null</code>
326+
* if not in a projection
327+
*/
328+
private IRegion fVisibleRegionDuringProjection;
295329
/** The queue of projection commands used to assess the costs of projection changes. */
296330
private ProjectionCommandQueue fCommandQueue;
297331
/**
@@ -301,6 +335,8 @@ private void computeExpectedExecutionCosts() {
301335
*/
302336
private int fDeletedLines;
303337

338+
private UpdateDocumentListener fUpdateDocumentListener= new UpdateDocumentListener();
339+
304340

305341
/**
306342
* Creates a new projection source viewer.
@@ -510,6 +546,11 @@ public final void disableProjection() {
510546
fProjectionAnnotationModel.removeAllAnnotations();
511547
fFindReplaceDocumentAdapter= null;
512548
fireProjectionDisabled();
549+
if (fVisibleRegionDuringProjection != null) {
550+
super.setVisibleRegion(fVisibleRegionDuringProjection.getOffset(), fVisibleRegionDuringProjection.getLength());
551+
fVisibleRegionDuringProjection= null;
552+
}
553+
getDocument().removeDocumentListener(fUpdateDocumentListener);
513554
}
514555
}
515556

@@ -518,9 +559,14 @@ public final void disableProjection() {
518559
*/
519560
public final void enableProjection() {
520561
if (!isProjectionMode()) {
562+
IRegion visibleRegion= getVisibleRegion();
521563
addProjectionAnnotationModel(getVisualAnnotationModel());
522564
fFindReplaceDocumentAdapter= null;
523565
fireProjectionEnabled();
566+
if (visibleRegion != null && (visibleRegion.getOffset() != 0 || visibleRegion.getLength() != 0) && visibleRegion.getLength() < getDocument().getLength()) {
567+
setVisibleRegion(visibleRegion.getOffset(), visibleRegion.getLength());
568+
}
569+
getDocument().addDocumentListener(fUpdateDocumentListener);
524570
}
525571
}
526572

@@ -529,6 +575,10 @@ private void expandAll() {
529575
IDocument doc= getDocument();
530576
int length= doc == null ? 0 : doc.getLength();
531577
if (isProjectionMode()) {
578+
if (fVisibleRegionDuringProjection != null) {
579+
offset= fVisibleRegionDuringProjection.getOffset();
580+
length= fVisibleRegionDuringProjection.getLength();
581+
}
532582
fProjectionAnnotationModel.expandAll(offset, length);
533583
}
534584
}
@@ -683,9 +733,28 @@ private int toLineStart(IDocument document, int offset, boolean testLastLine) th
683733

684734
@Override
685735
public void setVisibleRegion(int start, int length) {
686-
fWasProjectionEnabled= isProjectionMode();
687-
disableProjection();
688-
super.setVisibleRegion(start, length);
736+
if (isProjectionMode()) {
737+
try {
738+
int documentLength= getDocument().getLength();
739+
if (fVisibleRegionDuringProjection != null) {
740+
expand(0, fVisibleRegionDuringProjection.getOffset(), false);
741+
int oldEnd= fVisibleRegionDuringProjection.getOffset() + fVisibleRegionDuringProjection.getLength();
742+
expand(oldEnd, documentLength - oldEnd, false);
743+
}
744+
collapse(0, start, true);
745+
int end= start + length + 1;
746+
int endInvisibleRegionLength= documentLength - end;
747+
if (endInvisibleRegionLength > 0) {
748+
collapse(end, endInvisibleRegionLength, true);
749+
}
750+
fVisibleRegionDuringProjection= new Region(start, end - start);
751+
} catch (BadLocationException e) {
752+
e.printStackTrace();
753+
}
754+
fVisibleRegionDuringProjection= new Region(start, length);
755+
} else {
756+
super.setVisibleRegion(start, length);
757+
}
689758
}
690759

691760
@Override
@@ -710,6 +779,9 @@ public void resetVisibleRegion() {
710779

711780
@Override
712781
public IRegion getVisibleRegion() {
782+
if (fVisibleRegionDuringProjection != null) {
783+
return fVisibleRegionDuringProjection;
784+
}
713785
disableProjection();
714786
IRegion visibleRegion= getModelCoverage();
715787
if (visibleRegion == null)

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

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import org.eclipse.swt.dnd.Clipboard;
1919
import org.eclipse.swt.dnd.TextTransfer;
2020
import org.eclipse.swt.layout.FillLayout;
21+
import org.eclipse.swt.widgets.Composite;
2122
import org.eclipse.swt.widgets.Shell;
2223

2324
import org.eclipse.jface.text.BadLocationException;
@@ -29,12 +30,28 @@
2930
import org.eclipse.jface.text.Position;
3031
import org.eclipse.jface.text.Region;
3132
import org.eclipse.jface.text.source.AnnotationModel;
33+
import org.eclipse.jface.text.source.IOverviewRuler;
34+
import org.eclipse.jface.text.source.IVerticalRuler;
3235
import org.eclipse.jface.text.source.projection.IProjectionPosition;
3336
import org.eclipse.jface.text.source.projection.ProjectionAnnotation;
3437
import org.eclipse.jface.text.source.projection.ProjectionViewer;
3538

3639
public class ProjectionViewerTest {
3740

41+
/**
42+
* A {@link ProjectionViewer} that provides access to {@link #getVisibleDocument()}.
43+
*/
44+
private final class TestProjectionViewer extends ProjectionViewer {
45+
private TestProjectionViewer(Composite parent, IVerticalRuler ruler, IOverviewRuler overviewRuler, boolean showsAnnotationOverview, int styles) {
46+
super(parent, ruler, overviewRuler, showsAnnotationOverview, styles);
47+
}
48+
49+
@Override
50+
public IDocument getVisibleDocument() {
51+
return super.getVisibleDocument();
52+
}
53+
}
54+
3855
private static final class ProjectionPosition extends Position implements IProjectionPosition {
3956

4057
public ProjectionPosition(IDocument document) {
@@ -75,4 +92,159 @@ public void testCopyPaste() {
7592
shell.dispose();
7693
}
7794
}
95+
96+
@Test
97+
public void testVisibleRegionDoesNotChangeWithProjections() {
98+
Shell shell= new Shell();
99+
shell.setLayout(new FillLayout());
100+
ProjectionViewer viewer= new ProjectionViewer(shell, null, null, false, SWT.NONE);
101+
String documentContent= """
102+
Hello
103+
World
104+
123
105+
456
106+
""";
107+
Document document= new Document(documentContent);
108+
viewer.setDocument(document, new AnnotationModel());
109+
int regionLength= documentContent.indexOf('\n');
110+
viewer.setVisibleRegion(0, regionLength);
111+
viewer.enableProjection();
112+
viewer.getProjectionAnnotationModel().addAnnotation(new ProjectionAnnotation(false), new ProjectionPosition(document));
113+
shell.setVisible(true);
114+
try {
115+
assertEquals(0, viewer.getVisibleRegion().getOffset());
116+
assertEquals(regionLength, viewer.getVisibleRegion().getLength());
117+
118+
viewer.getTextOperationTarget().doOperation(ProjectionViewer.COLLAPSE_ALL);
119+
assertEquals(0, viewer.getVisibleRegion().getOffset());
120+
assertEquals(regionLength, viewer.getVisibleRegion().getLength());
121+
} finally {
122+
shell.dispose();
123+
}
124+
}
125+
126+
@Test
127+
public void testVisibleRegionProjectionCannotBeExpanded() {
128+
Shell shell= new Shell();
129+
shell.setLayout(new FillLayout());
130+
TestProjectionViewer viewer= new TestProjectionViewer(shell, null, null, false, SWT.NONE);
131+
String documentContent= """
132+
Hello
133+
World
134+
123
135+
456
136+
""";
137+
Document document= new Document(documentContent);
138+
viewer.setDocument(document, new AnnotationModel());
139+
int secondLineStart= documentContent.indexOf("World");
140+
int secondLineEnd= documentContent.indexOf('\n', secondLineStart);
141+
viewer.setVisibleRegion(secondLineStart, secondLineEnd - secondLineStart);
142+
viewer.enableProjection();
143+
shell.setVisible(true);
144+
try {
145+
assertEquals("World", viewer.getVisibleDocument().get());
146+
viewer.getTextOperationTarget().doOperation(ProjectionViewer.EXPAND_ALL);
147+
assertEquals("World", viewer.getVisibleDocument().get());
148+
} finally {
149+
shell.dispose();
150+
}
151+
}
152+
153+
@Test
154+
public void testVisibleRegionAddsProjectionAnnotationsIfProjectionsEnabled() {
155+
testProjectionAnnotationsFromVisibleRegion(true);
156+
}
157+
158+
@Test
159+
public void testEnableProjectionAddsProjectionAnnotationsIfVisibleRegionEnabled() {
160+
testProjectionAnnotationsFromVisibleRegion(false);
161+
}
162+
163+
private void testProjectionAnnotationsFromVisibleRegion(boolean enableProjectionFirst) {
164+
Shell shell= new Shell();
165+
shell.setLayout(new FillLayout());
166+
TestProjectionViewer viewer= new TestProjectionViewer(shell, null, null, false, SWT.NONE);
167+
String documentContent= """
168+
Hello
169+
World
170+
123
171+
456
172+
""";
173+
Document document= new Document(documentContent);
174+
viewer.setDocument(document, new AnnotationModel());
175+
int secondLineStart= documentContent.indexOf("World");
176+
int secondLineEnd= documentContent.indexOf('\n', secondLineStart);
177+
178+
shell.setVisible(true);
179+
if (enableProjectionFirst) {
180+
viewer.enableProjection();
181+
viewer.setVisibleRegion(secondLineStart, secondLineEnd - secondLineStart);
182+
} else {
183+
viewer.setVisibleRegion(secondLineStart, secondLineEnd - secondLineStart);
184+
viewer.enableProjection();
185+
}
186+
187+
// boolean startAnnotationFound= false;
188+
// boolean endAnnotationFound= false;
189+
// int annotationCount= 0;
190+
191+
try {
192+
assertEquals("World", viewer.getVisibleDocument().get().trim());
193+
194+
// for (Iterator<Annotation> it= viewer.getProjectionAnnotationModel().getAnnotationIterator(); it.hasNext();) {
195+
// Annotation annotation= it.next();
196+
// assertEquals("org.eclipse.jface.text.source.projection.ProjectionViewer.InvisibleCollapsedProjectionAnnotation", annotation.getClass().getCanonicalName());
197+
// Position position= viewer.getProjectionAnnotationModel().getPosition(annotation);
198+
//
199+
// if (position.getOffset() == 0) {
200+
// assertEquals("org.eclipse.jface.text.source.projection.ProjectionViewer.ExactRegionProjectionPosition", position.getClass().getCanonicalName());
201+
// assertEquals("Hello\n".length(), position.getLength());
202+
// startAnnotationFound= true;
203+
// } else {
204+
// assertEquals(secondLineEnd + 2, position.getOffset());
205+
// assertEquals(7, position.getLength());
206+
// endAnnotationFound= true;
207+
// }
208+
// annotationCount++;
209+
// }
210+
// assertEquals(2, annotationCount);
211+
// assertTrue(startAnnotationFound);
212+
// assertTrue(endAnnotationFound);
213+
} finally {
214+
shell.dispose();
215+
}
216+
}
217+
218+
@Test
219+
public void testInsertIntoVisibleRegion() throws BadLocationException {
220+
Shell shell= new Shell();
221+
shell.setLayout(new FillLayout());
222+
TestProjectionViewer viewer= new TestProjectionViewer(shell, null, null, false, SWT.NONE);
223+
String documentContent= """
224+
Hello
225+
World
226+
123
227+
456
228+
""";
229+
Document document= new Document(documentContent);
230+
viewer.setDocument(document, new AnnotationModel());
231+
int secondLineStart= documentContent.indexOf("World");
232+
int secondLineEnd= documentContent.indexOf('\n', secondLineStart);
233+
234+
shell.setVisible(true);
235+
236+
try {
237+
viewer.setVisibleRegion(secondLineStart, secondLineEnd - secondLineStart);
238+
viewer.enableProjection();
239+
240+
assertEquals("World", viewer.getVisibleDocument().get());
241+
242+
viewer.getDocument().replace(documentContent.indexOf("rld"), 0, "---");
243+
244+
assertEquals("Wo---rld", viewer.getVisibleDocument().get());
245+
} finally {
246+
shell.dispose();
247+
}
248+
}
249+
78250
}

0 commit comments

Comments
 (0)