Skip to content

Commit d2f4f5a

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 d2f4f5a

File tree

2 files changed

+258
-3
lines changed

2 files changed

+258
-3
lines changed

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

Lines changed: 86 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,39 @@ 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+
746+
int end= start + length + 1;
747+
// ensure that trailing whitespace is included
748+
while (end < documentLength && isWhitespaceButNotNewline(getDocument().getChar(end))) {
749+
end++;
750+
}
751+
end++;
752+
753+
int endInvisibleRegionLength= documentLength - end;
754+
if (endInvisibleRegionLength > 0) {
755+
collapse(end, endInvisibleRegionLength, true);
756+
}
757+
fVisibleRegionDuringProjection= new Region(start, end - start);
758+
} catch (BadLocationException e) {
759+
e.printStackTrace();
760+
}
761+
fVisibleRegionDuringProjection= new Region(start, length);
762+
} else {
763+
super.setVisibleRegion(start, length);
764+
}
765+
}
766+
767+
private boolean isWhitespaceButNotNewline(char c) {
768+
return Character.isWhitespace(c) && c != '\n' && c != '\r';
689769
}
690770

691771
@Override
@@ -710,6 +790,9 @@ public void resetVisibleRegion() {
710790

711791
@Override
712792
public IRegion getVisibleRegion() {
793+
if (fVisibleRegionDuringProjection != null) {
794+
return fVisibleRegionDuringProjection;
795+
}
713796
disableProjection();
714797
IRegion visibleRegion= getModelCoverage();
715798
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)