Skip to content

Commit 5274f99

Browse files
committed
Allow using visible regions with projections eclipse-platform#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 eclipse-platform#3074
1 parent d2177cf commit 5274f99

File tree

2 files changed

+249
-3
lines changed

2 files changed

+249
-3
lines changed

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

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@
2828
import org.eclipse.swt.dnd.TextTransfer;
2929
import org.eclipse.swt.dnd.Transfer;
3030
import org.eclipse.swt.events.VerifyEvent;
31+
import org.eclipse.swt.graphics.GC;
3132
import org.eclipse.swt.graphics.Point;
33+
import org.eclipse.swt.graphics.Rectangle;
34+
import org.eclipse.swt.widgets.Canvas;
3235
import org.eclipse.swt.widgets.Composite;
3336
import org.eclipse.swt.widgets.Display;
3437

@@ -272,6 +275,43 @@ private void computeExpectedExecutionCosts() {
272275
}
273276
}
274277

278+
/**
279+
* A {@link ProjectionAnnotation} that is always collapsed and invisible.
280+
*/
281+
private static class InvisibleCollapsedProjectionAnnotation extends ProjectionAnnotation {
282+
public InvisibleCollapsedProjectionAnnotation() {
283+
super(true);
284+
}
285+
286+
@Override
287+
public void paint(GC gc, Canvas canvas, Rectangle rectangle) {
288+
}
289+
}
290+
291+
/**
292+
* An {@link IProjectionPosition} that includes hiding the offset and length.
293+
*/
294+
private static class ExactRegionProjectionPosition extends Position implements IProjectionPosition {
295+
296+
public ExactRegionProjectionPosition(int offset, int length) {
297+
super(offset, length);
298+
}
299+
300+
@Override
301+
public IRegion[] computeProjectionRegions(IDocument document) throws BadLocationException {
302+
return new IRegion[] {
303+
new Region(getOffset(), getLength())
304+
};
305+
}
306+
307+
@Override
308+
public int computeCaptionOffset(IDocument document) throws BadLocationException {
309+
return 0;
310+
}
311+
312+
}
313+
314+
275315
/** The projection annotation model used by this viewer. */
276316
private ProjectionAnnotationModel fProjectionAnnotationModel;
277317
/** The annotation model listener */
@@ -292,6 +332,11 @@ private void computeExpectedExecutionCosts() {
292332
private IDocument fReplaceVisibleDocumentExecutionTrigger;
293333
/** <code>true</code> if projection was on the last time we switched to segmented mode. */
294334
private boolean fWasProjectionEnabled;
335+
/**
336+
* The region set by {@link #setVisibleRegion(int, int)} during projection or <code>null</code>
337+
* if not in a projection
338+
*/
339+
private IRegion fVisibleRegionDuringProjection;
295340
/** The queue of projection commands used to assess the costs of projection changes. */
296341
private ProjectionCommandQueue fCommandQueue;
297342
/**
@@ -510,6 +555,10 @@ public final void disableProjection() {
510555
fProjectionAnnotationModel.removeAllAnnotations();
511556
fFindReplaceDocumentAdapter= null;
512557
fireProjectionDisabled();
558+
if (fVisibleRegionDuringProjection != null) {
559+
super.setVisibleRegion(fVisibleRegionDuringProjection.getOffset(), fVisibleRegionDuringProjection.getLength());
560+
fVisibleRegionDuringProjection= null;
561+
}
513562
}
514563
}
515564

@@ -518,9 +567,13 @@ public final void disableProjection() {
518567
*/
519568
public final void enableProjection() {
520569
if (!isProjectionMode()) {
570+
IRegion visibleRegion= getVisibleRegion();
521571
addProjectionAnnotationModel(getVisualAnnotationModel());
522572
fFindReplaceDocumentAdapter= null;
523573
fireProjectionEnabled();
574+
if (visibleRegion != null) {
575+
setVisibleRegion(visibleRegion.getOffset(), visibleRegion.getLength());
576+
}
524577
}
525578
}
526579

@@ -529,6 +582,10 @@ private void expandAll() {
529582
IDocument doc= getDocument();
530583
int length= doc == null ? 0 : doc.getLength();
531584
if (isProjectionMode()) {
585+
if (fVisibleRegionDuringProjection != null) {
586+
offset= fVisibleRegionDuringProjection.getOffset();
587+
length= fVisibleRegionDuringProjection.getLength();
588+
}
532589
fProjectionAnnotationModel.expandAll(offset, length);
533590
}
534591
}
@@ -683,9 +740,24 @@ private int toLineStart(IDocument document, int offset, boolean testLastLine) th
683740

684741
@Override
685742
public void setVisibleRegion(int start, int length) {
686-
fWasProjectionEnabled= isProjectionMode();
687-
disableProjection();
688-
super.setVisibleRegion(start, length);
743+
if (isProjectionMode()) {
744+
for (Iterator<Annotation> annotationIterator= fProjectionAnnotationModel.getAnnotationIterator(); annotationIterator.hasNext();) {
745+
Annotation ann= annotationIterator.next();
746+
if (ann instanceof InvisibleCollapsedProjectionAnnotation) {
747+
fProjectionAnnotationModel.removeAnnotation(ann);
748+
}
749+
}
750+
if (start > 0) {
751+
fProjectionAnnotationModel.addAnnotation(new InvisibleCollapsedProjectionAnnotation(), new ExactRegionProjectionPosition(0, start));
752+
}
753+
int regionEnd= start + length;
754+
if (regionEnd < getDocument().getLength()) {
755+
fProjectionAnnotationModel.addAnnotation(new InvisibleCollapsedProjectionAnnotation(), new Position(regionEnd, getDocument().getLength() - regionEnd));
756+
}
757+
fVisibleRegionDuringProjection= new Region(start, length);
758+
} else {
759+
super.setVisibleRegion(start, length);
760+
}
689761
}
690762

691763
@Override
@@ -710,6 +782,9 @@ public void resetVisibleRegion() {
710782

711783
@Override
712784
public IRegion getVisibleRegion() {
785+
if (fVisibleRegionDuringProjection != null) {
786+
return fVisibleRegionDuringProjection;
787+
}
713788
disableProjection();
714789
IRegion visibleRegion= getModelCoverage();
715790
if (visibleRegion == null)

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

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,17 @@
1111
package org.eclipse.jface.text.tests;
1212

1313
import static org.junit.Assert.assertEquals;
14+
import static org.junit.Assert.assertTrue;
15+
16+
import java.util.Iterator;
1417

1518
import org.junit.Test;
1619

1720
import org.eclipse.swt.SWT;
1821
import org.eclipse.swt.dnd.Clipboard;
1922
import org.eclipse.swt.dnd.TextTransfer;
2023
import org.eclipse.swt.layout.FillLayout;
24+
import org.eclipse.swt.widgets.Composite;
2125
import org.eclipse.swt.widgets.Shell;
2226

2327
import org.eclipse.jface.text.BadLocationException;
@@ -28,13 +32,30 @@
2832
import org.eclipse.jface.text.ITextSelection;
2933
import org.eclipse.jface.text.Position;
3034
import org.eclipse.jface.text.Region;
35+
import org.eclipse.jface.text.source.Annotation;
3136
import org.eclipse.jface.text.source.AnnotationModel;
37+
import org.eclipse.jface.text.source.IOverviewRuler;
38+
import org.eclipse.jface.text.source.IVerticalRuler;
3239
import org.eclipse.jface.text.source.projection.IProjectionPosition;
3340
import org.eclipse.jface.text.source.projection.ProjectionAnnotation;
3441
import org.eclipse.jface.text.source.projection.ProjectionViewer;
3542

3643
public class ProjectionViewerTest {
3744

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

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

0 commit comments

Comments
 (0)