Skip to content

Commit 5b7cb97

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 5b7cb97

File tree

2 files changed

+282
-3
lines changed

2 files changed

+282
-3
lines changed

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

Lines changed: 111 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

@@ -99,6 +102,35 @@ public class ProjectionViewer extends SourceViewer implements ITextViewerExtensi
99102
*/
100103
public static final int COLLAPSE_ALL= BASE + 5;
101104

105+
private final class UpdateDocumentListener implements IDocumentListener {
106+
@Override
107+
public void documentChanged(DocumentEvent event) {
108+
if (fVisibleRegionDuringProjection != null) {
109+
int oldLength= event.getLength();
110+
int newLength= event.getText().length();
111+
int oldVisibleRegionEnd= fVisibleRegionDuringProjection.getOffset() + fVisibleRegionDuringProjection.getLength();
112+
113+
if (oldVisibleRegionEnd >= event.getOffset() && oldVisibleRegionEnd <= event.getOffset() + event.getLength()) {
114+
int newVisibleRegionEnd= event.getOffset() + newLength;
115+
fProjectionAnnotationModel.addAnnotation(new InvisibleCollapsedProjectionAnnotation(), new Position(newVisibleRegionEnd, getDocument().getLength() - newVisibleRegionEnd));
116+
fVisibleRegionDuringProjection= new Region(fVisibleRegionDuringProjection.getOffset(), newVisibleRegionEnd - fVisibleRegionDuringProjection.getOffset());
117+
}
118+
119+
if (event.getOffset() < fVisibleRegionDuringProjection.getOffset()) {
120+
fVisibleRegionDuringProjection= new Region(fVisibleRegionDuringProjection.getOffset() + newLength - oldLength, fVisibleRegionDuringProjection.getLength());
121+
} else {
122+
if (event.getOffset() + oldLength < oldVisibleRegionEnd) {
123+
fVisibleRegionDuringProjection= new Region(fVisibleRegionDuringProjection.getOffset(), fVisibleRegionDuringProjection.getLength() + newLength - oldLength);
124+
}
125+
}
126+
}
127+
}
128+
129+
@Override
130+
public void documentAboutToBeChanged(DocumentEvent event) {
131+
}
132+
}
133+
102134
/**
103135
* Internal listener to changes of the annotation model.
104136
*/
@@ -272,6 +304,43 @@ private void computeExpectedExecutionCosts() {
272304
}
273305
}
274306

307+
/**
308+
* A {@link ProjectionAnnotation} that is always collapsed and invisible.
309+
*/
310+
private static class InvisibleCollapsedProjectionAnnotation extends ProjectionAnnotation {
311+
public InvisibleCollapsedProjectionAnnotation() {
312+
super(true);
313+
}
314+
315+
@Override
316+
public void paint(GC gc, Canvas canvas, Rectangle rectangle) {
317+
}
318+
}
319+
320+
/**
321+
* An {@link IProjectionPosition} that includes hiding the offset and length.
322+
*/
323+
private static class ExactRegionProjectionPosition extends Position implements IProjectionPosition {
324+
325+
public ExactRegionProjectionPosition(int offset, int length) {
326+
super(offset, length);
327+
}
328+
329+
@Override
330+
public IRegion[] computeProjectionRegions(IDocument document) throws BadLocationException {
331+
return new IRegion[] {
332+
new Region(getOffset(), getLength())
333+
};
334+
}
335+
336+
@Override
337+
public int computeCaptionOffset(IDocument document) throws BadLocationException {
338+
return 0;
339+
}
340+
341+
}
342+
343+
275344
/** The projection annotation model used by this viewer. */
276345
private ProjectionAnnotationModel fProjectionAnnotationModel;
277346
/** The annotation model listener */
@@ -292,6 +361,11 @@ private void computeExpectedExecutionCosts() {
292361
private IDocument fReplaceVisibleDocumentExecutionTrigger;
293362
/** <code>true</code> if projection was on the last time we switched to segmented mode. */
294363
private boolean fWasProjectionEnabled;
364+
/**
365+
* The region set by {@link #setVisibleRegion(int, int)} during projection or <code>null</code>
366+
* if not in a projection
367+
*/
368+
private IRegion fVisibleRegionDuringProjection;
295369
/** The queue of projection commands used to assess the costs of projection changes. */
296370
private ProjectionCommandQueue fCommandQueue;
297371
/**
@@ -301,6 +375,8 @@ private void computeExpectedExecutionCosts() {
301375
*/
302376
private int fDeletedLines;
303377

378+
private UpdateDocumentListener fUpdateDocumentListener= new UpdateDocumentListener();
379+
304380

305381
/**
306382
* Creates a new projection source viewer.
@@ -510,6 +586,11 @@ public final void disableProjection() {
510586
fProjectionAnnotationModel.removeAllAnnotations();
511587
fFindReplaceDocumentAdapter= null;
512588
fireProjectionDisabled();
589+
if (fVisibleRegionDuringProjection != null) {
590+
super.setVisibleRegion(fVisibleRegionDuringProjection.getOffset(), fVisibleRegionDuringProjection.getLength());
591+
fVisibleRegionDuringProjection= null;
592+
}
593+
getDocument().removeDocumentListener(fUpdateDocumentListener);
513594
}
514595
}
515596

@@ -518,9 +599,14 @@ public final void disableProjection() {
518599
*/
519600
public final void enableProjection() {
520601
if (!isProjectionMode()) {
602+
IRegion visibleRegion= getVisibleRegion();
521603
addProjectionAnnotationModel(getVisualAnnotationModel());
522604
fFindReplaceDocumentAdapter= null;
523605
fireProjectionEnabled();
606+
if (visibleRegion != null) {
607+
setVisibleRegion(visibleRegion.getOffset(), visibleRegion.getLength());
608+
}
609+
getDocument().addDocumentListener(fUpdateDocumentListener);
524610
}
525611
}
526612

@@ -529,6 +615,10 @@ private void expandAll() {
529615
IDocument doc= getDocument();
530616
int length= doc == null ? 0 : doc.getLength();
531617
if (isProjectionMode()) {
618+
if (fVisibleRegionDuringProjection != null) {
619+
offset= fVisibleRegionDuringProjection.getOffset();
620+
length= fVisibleRegionDuringProjection.getLength();
621+
}
532622
fProjectionAnnotationModel.expandAll(offset, length);
533623
}
534624
}
@@ -683,9 +773,24 @@ private int toLineStart(IDocument document, int offset, boolean testLastLine) th
683773

684774
@Override
685775
public void setVisibleRegion(int start, int length) {
686-
fWasProjectionEnabled= isProjectionMode();
687-
disableProjection();
688-
super.setVisibleRegion(start, length);
776+
if (isProjectionMode()) {
777+
for (Iterator<Annotation> annotationIterator= fProjectionAnnotationModel.getAnnotationIterator(); annotationIterator.hasNext();) {
778+
Annotation ann= annotationIterator.next();
779+
if (ann instanceof InvisibleCollapsedProjectionAnnotation) {
780+
fProjectionAnnotationModel.removeAnnotation(ann);
781+
}
782+
}
783+
if (start > 0) {
784+
fProjectionAnnotationModel.addAnnotation(new InvisibleCollapsedProjectionAnnotation(), new ExactRegionProjectionPosition(0, start));
785+
}
786+
int regionEnd= start + length;
787+
if (regionEnd < getDocument().getLength()) {
788+
fProjectionAnnotationModel.addAnnotation(new InvisibleCollapsedProjectionAnnotation(), new Position(regionEnd, getDocument().getLength() - regionEnd));
789+
}
790+
fVisibleRegionDuringProjection= new Region(start, length);
791+
} else {
792+
super.setVisibleRegion(start, length);
793+
}
689794
}
690795

691796
@Override
@@ -710,6 +815,9 @@ public void resetVisibleRegion() {
710815

711816
@Override
712817
public IRegion getVisibleRegion() {
818+
if (fVisibleRegionDuringProjection != null) {
819+
return fVisibleRegionDuringProjection;
820+
}
713821
disableProjection();
714822
IRegion visibleRegion= getModelCoverage();
715823
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)