Skip to content

Commit 5274f99

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 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)