Skip to content

Commit 5b7cb97

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